-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Testing v6 w/ React Testing Library Docs #7169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
06172a0
8b01244
352bdcc
d8a2398
dc28df5
ba6825d
4fd1541
e5f95f1
3533f61
6556244
5bc9025
995e313
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,117 @@ | ||
# Testing With React Testing Library | ||
# Testing With React Testing Library (RTL) | ||
|
||
## Getting Setup | ||
|
||
TODO | ||
This guide assumes you followed the instructions for [Adding React Router via Create React App](../../installation/add-to-a-website.md#create-react-app). If you did not start with Create React App, but still have the same application structure, you can install [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) by following [their installation guide](https://github.com/testing-library/react-testing-library#installation). | ||
|
||
## Basic Test | ||
|
||
A basic rendering test can be important to esnure that we have everything installed and set up correctly. Fortunately, this is easy to do with RTL. | ||
|
||
Since we've wrapped our `App` component in the `Router` in our `index.js` file, we do have to wrap it in each of our isolated component tests, otherwise `history` will be undefined. If the `Router` had been inside of our `App`, then we would not have to wrap it inside of our tests. | ||
|
||
A recommended simple test looks like the following: | ||
|
||
```jsx | ||
test('renders react router header', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
const header = getByText('Welcome to React Router!'); | ||
expect(header).toBeInTheDocument(); | ||
}); | ||
``` | ||
|
||
This ensures that we can render our `App` component and that the `h1` we put in during our setup guide is in the document. | ||
|
||
## Testing Routes and Redirects | ||
|
||
TODO | ||
|
||
## Testing Links and Navigation | ||
|
||
TODO | ||
Testing a link and the subsequent navigation with React Testing Library is as easy as firing a click event on the link itself and asserting on the window's location to test that it worked. | ||
|
||
This is accomplished like so: | ||
|
||
```jsx | ||
it('goes to about when link clicked', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
|
||
const aboutLink = getByText('About'); | ||
act(() => { | ||
fireEvent.click(aboutLink); | ||
}); | ||
|
||
expect(window.location.pathname).toBe('/about'); | ||
}); | ||
``` | ||
|
||
Since our setup guide then replaces the about link with a link back to the home page, we can easily test that we end up back on the home page by triggering a second click, this time on the home link: | ||
|
||
```jsx | ||
it('goes to about when link clicked and then back to home when link clicked', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
|
||
const aboutLink = getByText('About'); | ||
act(() => { | ||
fireEvent.click(aboutLink); | ||
}); | ||
expect(window.location.pathname).toBe('/about'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't be the case if you use an in-memory router (it does not update the window.location). I think there are different opinions about this. The downside of using a "full" router is that JSDOM doesn't give you a way to reset the navigation stack, so if you do something like "go back 3 times" you could end up on a previous test's URL (even if you reset the initial location as described below, the number of items on the stack doesn't reset). On the other hand, memory router doesn't represent what's going in the actual app as well, especially if you have some regular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using MemoryRouter in my tests and using a test component as a the route to redirect to.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if you test the page contents, you can make an assertion. But you can't assert There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why I've moved away from using the MemoryRouter and instead I render the same router that's rendered in my source code and interact with window.location directly instead: https://github.com/kentcdodds/bookshelf/blob/d9d70fceaea790dfe57ee5a46c13f9f9cc810675/src/test/app-test-utils.js#L15 Works way better. And I get more confidence out of my tests. |
||
|
||
const homeLink = getByText('Home'); | ||
act(() => { | ||
fireEvent.click(homeLink); | ||
}); | ||
expect(window.location.pathname).toBe('/'); | ||
}); | ||
``` | ||
|
||
This test will initially fail if we run it right after the previous test. This is because the history stack still thinks we are on the about page. To fix this, we can use Jest's `beforeEach` function to replace our history state using: `window.history.replaceState({}, '', '/');`. This makes `react-router` think that we are on the home page when we start each test. Now, all three tests should be passing when ran together. | ||
|
||
Below is the full file to help see all of the tests together: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup, act } from '@testing-library/react'; | ||
import App from './App'; | ||
import { BrowserRouter as Router } from "react-router-dom"; | ||
|
||
beforeEach(() => { | ||
window.history.replaceState({}, '', '/'); | ||
}); | ||
|
||
afterEach(cleanup); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, and |
||
|
||
test('renders react router header', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
const header = getByText('Welcome to React Router!'); | ||
expect(header).toBeInTheDocument(); | ||
}); | ||
|
||
it('goes to about when link clicked', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
|
||
const aboutLink = getByText('About'); | ||
act(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't need to use You should rarely need to use |
||
fireEvent.click(aboutLink); | ||
}); | ||
|
||
expect(window.location.pathname).toBe('/about'); | ||
}); | ||
|
||
it('goes to about when link clicked and then back to home when link clicked', () => { | ||
const { getByText } = render(<Router><App /></Router>); | ||
|
||
const aboutLink = getByText('About'); | ||
act(() => { | ||
fireEvent.click(aboutLink); | ||
}); | ||
expect(window.location.pathname).toBe('/about'); | ||
|
||
const homeLink = getByText('Home'); | ||
act(() => { | ||
fireEvent.click(homeLink); | ||
}); | ||
expect(window.location.pathname).toBe('/'); | ||
}); | ||
|
||
``` |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're presently recommending people import
screen
from RTL instead of destructuring the render result. This works better with autocomplete and makes each test a little simpler.👀 @kentcdodds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, so this would be:
It would be even better to recommend the
wrapper
option sorerender
works as expected:And then to take it to the next level, there's a custom render: https://testing-library.com/docs/react-testing-library/setup#custom-render
Hope that context is helpful!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that in the repo README @alexkrolick but not in the docs site, the destructuring is still there, which is what I've been going off of for a while.
I can definitely update these examples, and I can also put in a PR for the RTL docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, we're still in the process of updating things to the new recommendations. PRs would be appreciated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, I can definitely help with that.
Thanks for all the help with this! I'll work on some updates for this tonight.