From 77c5515762826c029da05f1abf0e0dbeb8171173 Mon Sep 17 00:00:00 2001 From: Alex Krolick Date: Wed, 13 Feb 2019 01:06:15 -0800 Subject: [PATCH] Add guides for tricky topics --- docs/api-async.md | 6 +- docs/api-events.md | 2 +- docs/faq.md | 33 +++----- docs/guide-disappearance.md | 79 +++++++++++++++++++ docs/guide-which-query.md | 49 ++++++++++++ docs/react-testing-library/faq.md | 115 +--------------------------- docs/react-testing-library/setup.md | 14 ++-- website/sidebars.json | 1 + 8 files changed, 155 insertions(+), 144 deletions(-) create mode 100644 docs/guide-disappearance.md create mode 100644 docs/guide-which-query.md diff --git a/docs/api-async.md b/docs/api-async.md index f0bbe3245..fcca5e2ea 100644 --- a/docs/api-async.md +++ b/docs/api-async.md @@ -1,8 +1,12 @@ --- id: api-async -title: Async +title: Async Utilities --- +Several utilities are provided for dealing with asynchronous code. These can be +useful to wait for an element to appear or disappear in response to an action. +(See the [guide to testing disappearance](guide-disappearance.md).) + ### `wait` ```typescript diff --git a/docs/api-events.md b/docs/api-events.md index 7b778d1e4..01be18a38 100644 --- a/docs/api-events.md +++ b/docs/api-events.md @@ -1,6 +1,6 @@ --- id: api-events -title: Events +title: Firing Events --- ## `fireEvent` diff --git a/docs/faq.md b/docs/faq.md index 704cbca2c..cd00f329f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -7,27 +7,7 @@ title: FAQ Which get method should I use? -Based on [the Guiding Principles](guiding-principles.md), your test should -resemble how your code (component, page, etc.) as much as possible. With this in -mind, we recommend this order of priority: - -1. `getByLabelText`: Only really good for form fields, but this is the number 1 - method a user finds those elements, so it should be your top preference. -2. `getByPlaceholderText`: - [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). - But if that's all you have, then it's better than alternatives. -3. `getByText`: Not useful for forms, but this is the number 1 method a user - finds other elements (like buttons to click), so it should be your top - preference for non-form elements. -4. `getByAltText`: If your element is one which supports `alt` text (`img`, - `area`, and `input`), then you can use this to find that element. -5. `getByTestId`: The user cannot see (or hear) these, so this is only - recommended for cases where you can't match by text or it doesn't make sense - (the text is dynamic). - -Other than that, you can also use the `container` to query the rendered -component as well (using the regular -[`querySelector` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)). +See [Which Query Should I Use](guide-which-query.md) @@ -35,8 +15,13 @@ component as well (using the regular Can I write unit tests with this library? -Definitely yes! You can write unit, integration, functional, and end-to-end -tests with this library. +Definitely yes! You can write unit, integration, and end-to-end tests with this +library. + +As you write your tests, keep in mind: + +> The more your tests resemble the way your software is used, the more +> confidence they can give you. - [17 Feb 2018][guiding-principle] @@ -47,7 +32,7 @@ tests with this library. This is fairly common. Our first bit of advice is to try to get the default text used in your tests. That will make everything much easier (more than just using this utility). If that's not possible, then you're probably best to just stick -with `data-testid`s (which is not too bad anyway). +with `data-testid`s (which is not bad anyway). diff --git a/docs/guide-disappearance.md b/docs/guide-disappearance.md new file mode 100644 index 000000000..967946b91 --- /dev/null +++ b/docs/guide-disappearance.md @@ -0,0 +1,79 @@ +--- +id: guide-disappearance +title: Appearance and Disappearance +--- + +Sometimes you need to test that an element is present and then disappears or +vice versa. + +## Waiting for appearance + +If you need to wait for an element to appear, the +[async wait utilities](api-async.md) allow you to wait for an assertion to be +satisfied before proceeding. The wait utilities retry until the query passes or +times out. + +```jsx +test('movie title appears', async () => { + // element is initially not present... + + // wait for appearance + await wait(() => { + expect(getByText('the lion king')).toBeInTheDocument() + }) + + // wait for appearance and return the element + const movie = await waitForElement(() => getByText('the lion king')) +}) +``` + +## Waiting for disappearance + +The `wait` [async helper](api-async.md) function retries until the wrapped +function stops throwing an error. This can be used to assert that an element +disappears from the page. + +```jsx +test('movie title goes away', async () => { + // element is initially present... + // note use of queryBy instead of getBy to return null + // instead of throwing in the query itself + await wait(() => { + expect(queryByText('i, robot')).not.toBeInTheDocument() + }) +}) +``` + +## Asserting elements are not present + +The standard `getBy` methods throw an error when they can't find an element, so +if you want to make an assertion that an element is _not_ present in the DOM, +you can use `queryBy` APIs instead: + +```javascript +const submitButton = queryByText(container, 'submit') +expect(submitButton).toBeNull() // it doesn't exist +``` + +The `queryAll` APIs version return an array of matching nodes. The length of the +array can be useful for assertions after elements are added or removed from the +DOM. + +```javascript +const submitButtons = queryAllByText(container, 'submit') +expect(submitButtons).toHaveLength(2) // expect 2 elements +``` + +### `not.toBeInTheDocument` + +The [`jest-dom`](ecosystem-jest-dom.md) utility library provides the +`.toBeInTheDocument()` matcher, which can be used to assert that an element is +in the body of the document, or not. This can be more meaningful than asserting +a query result is `null`. + +```javascript +import 'jest-dom/extend-expect' +// use `queryBy` to avoid throwing an error with `getBy` +const submitButton = queryByText(container, 'submit') +expect(submitButton).not.toBeInTheDocument() +``` diff --git a/docs/guide-which-query.md b/docs/guide-which-query.md new file mode 100644 index 000000000..8d3ef13fc --- /dev/null +++ b/docs/guide-which-query.md @@ -0,0 +1,49 @@ +--- +id: guide-which-query +title: Which query should I use? +--- + +## Priority + +Based on [the Guiding Principles](guiding-principles.md), your test should +resemble how users interact with your code (component, page, etc.) as much as +possible. With this in mind, we recommend this order of priority: + +1. **Queries Accessible to Everyone** queries that reflect the experience of + visual/mouse users as well as those that use assistive technology + 1. `getByLabelText`: Only really good for form fields, but this is the number + one method a user finds those elements, so it should be your top + preference. + 1. `getByPlaceholderText`: + [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). + But if that's all you have, then it's better than alternatives. + 1. `getByText`: Not useful for forms, but this is the number 1 method a user + finds other elements (like buttons to click), so it should be your top + preference for non-form elements. + 1. `getByDisplayValue`: The current value of a form element can be useful + when navigating a page with filled-in values. +1. **Semantic Queries** HTML5 and ARIA compliant selectors + 1. `getByAltText`: If your element is one which supports `alt` text (`img`, + `area`, and `input`), then you can use this to find that element. + 1. `getByTitle`: The title attribute is usually more accessible by screen + readers than mouse/visual users + 1. `getByRole`: This can be used to select dialog boxes and other + difficult-to-capture elements in a more semantic way +1. **Test IDs** + 1. `getByTestId`: The user cannot see (or hear) these, so this is only + recommended for cases where you can't match by text or it doesn't make + sense (the text is dynamic). + +## Manual Queries + +On top of the queries provided by the testing library, you can using the regular +[`querySelector` DOM API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) +to query elements. Note that using this as an escape hatch to query by class or +id is a bad practice because users can't see or identify these attributes. Use a +testid if you have to. + +```jsx +// react-testing-library +const { container } = render() +const foo = container.querySelector(['data-foo="bar"']) +``` diff --git a/docs/react-testing-library/faq.md b/docs/react-testing-library/faq.md index 8e87ec26f..3d2db7379 100644 --- a/docs/react-testing-library/faq.md +++ b/docs/react-testing-library/faq.md @@ -3,6 +3,8 @@ id: faq title: FAQ --- +See also the [main FAQ](/docs/faq) for questions not specific to React testing +
How do I test input onChange handlers? @@ -62,34 +64,6 @@ as part of the `change` method call.
-Which get method should I use? - -Based on [the Guiding Principles](./guiding-principles), your test should -resemble how your code (component, page, etc.) is used as much as possible. With -this in mind, we recommend this order of priority: - -1. `getByLabelText`: Only really good for form fields, but this is the number 1 - method a user finds those elements, so it should be your top preference. -2. `getByPlaceholderText`: - [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). - But if that's all you have, then it's better than alternatives. -3. `getByText`: Not useful for forms, but this is the number 1 method a user - finds other elements (like buttons to click), so it should be your top - preference for non-form elements. -4. `getByAltText`: If your element is one which supports `alt` text (`img`, - `area`, and `input`), then you can use this to find that element. -5. `getByTestId`: The user cannot see (or hear) these, so this is only - recommended for cases where you can't match by text or it doesn't make sense - (the text is dynamic). - -Other than that, you can also use the `container` to query the rendered -component as well (using the regular -[`querySelector` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)). - -
- -
- Can I write unit tests with this library? Definitely yes! You can write unit and integration tests with this library. See @@ -107,17 +81,6 @@ As you write your tests, keep in mind:
-What if my app is localized and I don't have access to the text in test? - -This is fairly common. Our first bit of advice is to try to get the default text -used in your tests. That will make everything much easier (more than just using -this utility). If that's not possible, then you're probably best to just stick -with `data-testid`s (which is not bad anyway). - -
- -
- If I can't use shallow rendering, how do I mock out components in tests? In general, you should avoid mocking out components (see @@ -169,80 +132,6 @@ Learn more about how Jest mocks work from my blog post:
-What if I want to verify that an element does NOT exist? - -You typically will get access to rendered elements using the `getByTestId` -utility. However, that function will throw an error if the element isn't found. -If you want to specifically test for the absence of an element, then you should -use the `queryByTestId` utility which will return the element if found or `null` -if not. - -```javascript -expect(queryByTestId('thing-that-does-not-exist')).toBeNull() -``` - -
- -
- -I really don't like data-testids, but none of the other queries make sense. Do I have to use a data-testid? - -Definitely not. That said, a common reason people don't like the `data-testid` -attribute is they're concerned about shipping that to production. I'd suggest -that you probably want some simple E2E tests that run in production on occasion -to make certain that things are working smoothly. In that case the `data-testid` -attributes will be very useful. Even if you don't run these in production, you -may want to run some E2E tests that run on the same code you're about to ship to -production. In that case, the `data-testid` attributes will be valuable there as -well. - -All that said, if you really don't want to ship `data-testid` attributes, then -you can use -[this simple babel plugin](https://www.npmjs.com/package/babel-plugin-react-remove-properties) -to remove them. - -If you don't want to use them at all, then you can simply use regular DOM -methods and properties to query elements off your container. - -```javascript -const firstLiInDiv = container.querySelector('div li') -const allLisInDiv = container.querySelectorAll('div li') -const rootElement = container.firstChild -``` - -
- -
- -What if I’m iterating over a list of items that I want to put the data-testid="item" attribute on. How do I distinguish them from each other? - -You can make your selector just choose the one you want by including :nth-child -in the selector. - -```javascript -const thirdLiInUl = container.querySelector('ul > li:nth-child(3)') -``` - -Or you could include the index or an ID in your attribute: - -```javascript -
  • {item.text}
  • -``` - -And then you could use the `getByTestId` utility: - -```javascript -const items = [ - /* your items */ -] -const { getByTestId } = render(/* your component with the items */) -const thirdItem = getByTestId(`item-${items[2].id}`) -``` - -
    - -
    - What about enzyme is "bloated with complexity and features" and "encourage poor testing practices"? diff --git a/docs/react-testing-library/setup.md b/docs/react-testing-library/setup.md index 8e280f3ed..bd8d0fd3a 100644 --- a/docs/react-testing-library/setup.md +++ b/docs/react-testing-library/setup.md @@ -18,7 +18,7 @@ the setup and teardown of tests in individual files. For example, you can ensure [`cleanup`](./api#cleanup) is called after each test and import additional assertions. -To do this with Jest 24, you can add the +To do this with Jest 24 and up, you can add the [`setupFilesAfterEnv`](https://jestjs.io/docs/en/configuration.html#setupfilesafterenv-array) option to your Jest config. @@ -33,12 +33,14 @@ module.exports = { } ``` -### Jest 23 +### Older versions of Jest -Jest 23 uses the +
    + +Jest versions 23 and below use the [`setupTestFrameworkScriptFile`](https://jestjs.io/docs/en/23.x/configuration#setuptestframeworkscriptfile-string) -option in your Jest config. This setup file can be anywhere, for example -`jest.setup.js` or `./utils/setupTests.js`. +option in your Jest config instead of `setupFilesAfterEnv`. This setup file can +be anywhere, for example `jest.setup.js` or `./utils/setupTests.js`. If you are using the default setup from create-react-app, this option is set to `src/setupTests.js`. You should create this file if it doesn't exist and put the @@ -62,6 +64,8 @@ import 'jest-dom/extend-expect' import 'react-testing-library/cleanup-after-each' ``` +
    + ## Custom Render It's often useful to define a custom render method that includes things like diff --git a/website/sidebars.json b/website/sidebars.json index e5da5f9cd..ff964705e 100755 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -19,6 +19,7 @@ "example-external" ], "API": ["api-queries", "api-events", "api-async", "api-helpers"], + "Guides": ["guide-which-query", "guide-disappearance"], "Wrappers": [ { "type": "subcategory",