-
-
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
Closed
Closed
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
06172a0
Add setup, basic test, and link tests
thatzacdavis 8b01244
Fixing title
thatzacdavis 352bdcc
Update language used in RTL docs
thatzacdavis d8a2398
Update RTL test examples to use `screen` instead of destructured render
thatzacdavis dc28df5
Use wrapper param in RTL tests to support rerender function
thatzacdavis ba6825d
content: edited and added content
will-t-harris 4fd1541
content: change test names
will-t-harris e5f95f1
content: add basic redirect example
will-t-harris 3533f61
Rename App to HomePage in RTL testing docs
thatzacdavis 6556244
Explicitly show imports per RTL test in examples
thatzacdavis 5bc9025
Add working redirect test
thatzacdavis 995e313
refactor RedirectHandler component
will-t-harris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
164 changes: 159 additions & 5 deletions
164
docs/advanced-guides/testing/testing-with-react-testing-library.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,167 @@ | ||
# Testing With React Testing Library | ||
# Testing With React Testing Library (RTL) | ||
|
||
`react-testing-library` is a lightweight family of packages that help us test UI components in a manner that resembles the way users interact with our applications. | ||
|
||
To quote their (very excellent) documentation: | ||
|
||
The more your tests resemble the way your software is used, the more confidence they can give you. | ||
|
||
https://testing-library.com/docs/intro | ||
|
||
## 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). | ||
|
||
## Testing Routes and Redirects | ||
## Basic Test | ||
|
||
A basic rendering test ensures that we have everything installed and set up correctly. Fortunately, RTL gives us the tools to accomplish this. | ||
|
||
Since we've wrapped our `HomePage` (renamed from `App` after following the aforementioned instructions) component in the `Router` in our `index.js` file, we do have to wrap it around each of our individual 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. | ||
|
||
TODO | ||
A recommended test to ensure basic functionality looks like the following: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import HomePage from './HomePage'; | ||
import { BrowserRouter as Router } from 'react-router-dom'; | ||
|
||
test('<HomePage /> renders successfully', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
const header = screen.getByText('Welcome to React Router!'); | ||
expect(header).toBeInTheDocument(); | ||
}); | ||
``` | ||
|
||
This test ensures that the `App` component renders, and that the `h1` we added during the setup guide exists in the document. | ||
|
||
## Testing Links and Navigation | ||
|
||
TODO | ||
`react-router` has a lot of tests verifying that the routes work when the location changes, so you probably don't need to test this stuff. | ||
|
||
If you need to test navigation within your app, you can do so by firing a click event on the link itself and asserting that the path changed to the expected value. | ||
|
||
This is accomplished like so: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { render, fireEvent, screen } from '@testing-library/react'; | ||
import HomePage from './HomePage'; | ||
import { BrowserRouter as Router } from 'react-router-dom'; | ||
|
||
it('navigates to /about', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
|
||
const aboutLink = screen.getByText('About'); | ||
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 test navigating back to the home page by triggering another click, this time on the home link: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { render, fireEvent, screen } from '@testing-library/react'; | ||
import HomePage from './HomePage'; | ||
import { BrowserRouter as Router } from 'react-router-dom'; | ||
|
||
it('navigates to /about and back to /', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
|
||
const aboutLink = screen.getByText('About'); | ||
fireEvent.click(aboutLink); | ||
|
||
expect(window.location.pathname).toBe('/about'); | ||
|
||
const homeLink = screen.getByText('Home'); | ||
fireEvent.click(homeLink); | ||
|
||
expect(window.location.pathname).toBe('/'); | ||
}); | ||
``` | ||
|
||
This test will initially fail if it is run immediately 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 run together. | ||
|
||
Below is the full file with all of the tests together: | ||
|
||
`HomePage.test.js` | ||
```jsx | ||
import React from 'react'; | ||
import { render, fireEvent, screen } from '@testing-library/react'; | ||
import HomePage from './HomePage'; | ||
import { BrowserRouter as Router } from 'react-router-dom'; | ||
|
||
beforeEach(() => { | ||
window.history.replaceState({}, '', '/'); | ||
}); | ||
|
||
test('<HomePage /> renders successfully', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
const header = screen.getByText('Welcome to React Router!'); | ||
expect(header).toBeInTheDocument(); | ||
}); | ||
|
||
it('navigates to /about', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
|
||
const aboutLink = screen.getByText('About'); | ||
fireEvent.click(aboutLink); | ||
|
||
expect(window.location.pathname).toBe('/about'); | ||
}); | ||
|
||
it('navigates to /about and back to /', () => { | ||
render(<HomePage />, { wrapper: Router }); | ||
|
||
const aboutLink = screen.getByText('About'); | ||
fireEvent.click(aboutLink); | ||
|
||
expect(window.location.pathname).toBe('/about'); | ||
|
||
const homeLink = screen.getByText('Home'); | ||
fireEvent.click(homeLink); | ||
|
||
expect(window.location.pathname).toBe('/'); | ||
}); | ||
``` | ||
|
||
## Testing Routes and Redirects | ||
|
||
Let's say that we've built a redirect component that deals with re-routing the user from `/the-old-thing` to `/the-new-thing` | ||
|
||
`RedirectHandler.js` | ||
```jsx | ||
import React from 'react' | ||
import { Redirect } from 'react-router-dom' | ||
|
||
const RedirectHandler = () => <Redirect from="/the-old-thing" to="the-new-thing" />; | ||
|
||
export default RedirectHandler; | ||
``` | ||
|
||
And here we have one method of testing that the redirect is behaving as expected. | ||
|
||
`RedirectHandler.test.js` | ||
```jsx | ||
import React from "react"; | ||
import { BrowserRouter } from "react-router-dom"; | ||
import { render } from "@testing-library/react"; | ||
import RedirectHandler from "./RedirectHandler"; | ||
|
||
it("redirects /the-old-thing to /the-new-thing", () => { | ||
window.history.pushState({}, "The Old Thing", "/the-new-thing"); | ||
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. Do we want to Am I misunderstanding how |
||
|
||
render( | ||
<BrowserRouter> | ||
<RedirectHandler /> | ||
</BrowserRouter> | ||
); | ||
|
||
expect(window.location.pathname).toBe("/the-new-thing"); | ||
}); | ||
|
||
``` | ||
|
||
This test initializes the `BrowserRouter` with an initial path of `/the-old-thing` and verifies that the `RedirectHandler` correctly changes the path to `/the-new-thing`. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
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
<a>
tags mixed in or manual manipulation of the URL.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'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 comment
The 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
window.location.pathname
with MemoryRouter, because it doesn't update that.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.
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.