diff --git a/.all-contributorsrc b/.all-contributorsrc index 91f0d2cd..5bd7184b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -96,6 +96,16 @@ "contributions": [ "doc" ] + }, + { + "login": "lgandecki", + "name": "ลukasz Gandecki", + "avatar_url": "https://avatars1.githubusercontent.com/u/4002543?v=4", + "profile": "http://team.thebrain.pro", + "contributions": [ + "code", + "test" + ] } ] } diff --git a/README.md b/README.md index d1809fef..3627c8ac 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] @@ -78,8 +78,11 @@ facilitate testing implementation details). Read more about this in * [Usage](#usage) * [`Simulate`](#simulate) * [`flushPromises`](#flushpromises) + * [`waitForExpect`](#waitforexpect) * [`render`](#render) * [Custom Jest Matchers](#custom-jest-matchers) + * [`toBeInTheDOM`](#tobeinthedom) + * [`toHaveTextContent`](#tohavetextcontent) * [`TextMatch`](#textmatch) * [`query` APIs](#query-apis) * [Examples](#examples) @@ -151,6 +154,35 @@ you make your test function an `async` function and use See an example in the section about `render` below. +### `waitForExpect` + +Defined as: + +```javascript +waitForExpect(expectation: () => void, timeout?: number, interval?: number) => Promise<{}>; +``` + +When in need to wait for non-deterministic periods of time you can use waitForExpect, +to wait for your expectations to pass. Take a look at [`Is there a different way to wait for things to happen?`](#waitForExpect) part of the FAQ, +or the function documentation here: [`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) +or just take a look at this simple example: + +```javascript +... +await waitForExpect(() => expect(queryByLabelText('username')).not.toBeNull()) +getByLabelText('username').value = 'chucknorris' +... +``` + +Another advantage of waitForExpect in comparison to flushPromises, is that +flushPromises will not flush promises that have not been queued up already, +for example, if they will queue up as a result of the initial promises. +In consequence of that, you might have to call flushPromises multiple times to get your components +to your desired state. + +This can happen for example, when you integration test your apollo-connected react components +that go a couple level deep, with queries fired up in consequent components. + ### `render` In the example above, the `render` method returns an object that has a few @@ -591,6 +623,36 @@ that this is only effective if you've mocked out your async requests to resolve immediately (like the `axios` mock we have in the examples). It will not `await` for promises that are not already resolved by the time you attempt to flush them. +In case this doesn't work for you the way you would expect, take a look at the +waitForExpect function that should be much more intuitive to use. + + + +
+ +Is there a different way to wait for things to happen? For example for end to end or contract tests? +Definitely! There is an abstraction called `waitForExpect` that will keep +calling your expectations until a timeout or the expectation passes - whatever happens first. + +Please take a look at this example (taken from [`here`](https://github.com/kentcdodds/react-testing-library/blob/master/src/__tests__/end-to-end.js)): + +```javascript +import {render, waitForExpect} from 'react-testing-library' +test('it waits for the data to be loaded', async () => { + const {queryByText, queryByTestId} = render() + + // Initially the loader shows + expect(queryByText('Loading...')).toBeTruthy() + + // This will pass when the state of the component changes once the data is available + // the loader will disappear, and the data will be shown + await waitForExpect(() => expect(queryByText('Loading...')).toBeNull()) + expect(queryByTestId('message').textContent).toMatch(/Hello World/) +}) +``` + +For consistency and making your tests easier to understand, you can use it instead of flushPromises. +
## Other Solutions @@ -634,7 +696,7 @@ Thanks goes to these people ([emoji key][emojis]): | [
Kent C. Dodds](https://kentcdodds.com)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Code") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Documentation") [๐Ÿš‡](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Tests") | [
Ryan Castner](http://audiolion.github.io)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=audiolion "Documentation") | [
Daniel Sandiego](https://www.dnlsandiego.com)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=dnlsandiego "Code") | [
Paweล‚ Mikoล‚ajczyk](https://github.com/Miklet)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=Miklet "Code") | [
Alejandro ร‘รกรฑez Ortiz](http://co.linkedin.com/in/alejandronanez/)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=alejandronanez "Documentation") | [
Matt Parrish](https://github.com/pbomb)
[๐Ÿ›](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Apbomb "Bug reports") [๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Code") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Documentation") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Tests") | [
Justin Hall](https://github.com/wKovacs64)
[๐Ÿ“ฆ](#platform-wKovacs64 "Packaging/porting to new platform") | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| [
Anto Aravinth](https://github.com/antoaravinth)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [
Jonah Moses](https://github.com/JonahMoses)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | +| [
Anto Aravinth](https://github.com/antoaravinth)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [
Jonah Moses](https://github.com/JonahMoses)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | [
ลukasz Gandecki](http://team.thebrain.pro)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Code") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Tests") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Documentation") | diff --git a/package.json b/package.json index 77d2e484..e7cc1463 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "keywords": [], "author": "Kent C. Dodds (http://kentcdodds.com/)", "license": "MIT", - "dependencies": {}, + "dependencies": { + "wait-for-expect": "0.4.0" + }, "devDependencies": { "@types/react-dom": "^16.0.4", "axios": "^0.18.0", diff --git a/src/__tests__/end-to-end.js b/src/__tests__/end-to-end.js new file mode 100644 index 00000000..435f4a6e --- /dev/null +++ b/src/__tests__/end-to-end.js @@ -0,0 +1,40 @@ +import React from 'react' +import {render, waitForExpect} from '../' + +const fetchAMessage = () => + new Promise(resolve => { + // we are using random timeout here to simulate a real-time example + // of an async operation calling a callback at a non-deterministic time + const randomTimeout = Math.floor(Math.random() * 100) + setTimeout(() => { + resolve({returnedMessage: 'Hello World'}) + }, randomTimeout) + }) + +class ComponentWithLoader extends React.Component { + state = {loading: true} + async componentDidMount() { + const data = await fetchAMessage() + this.setState({data, loading: false}) // eslint-disable-line + } + render() { + if (this.state.loading) { + return
Loading...
+ } else { + return ( +
+ Loaded this message: {this.state.data.returnedMessage}! +
+ ) + } + } +} + +test('it waits for the data to be loaded', async () => { + const {queryByText, queryByTestId} = render() + + expect(queryByText('Loading...')).toBeTruthy() + + await waitForExpect(() => expect(queryByText('Loading...')).toBeNull()) + expect(queryByTestId('message').textContent).toMatch(/Hello World/) +}) diff --git a/src/index.js b/src/index.js index fd46ac9e..1fd4a9a9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import ReactDOM from 'react-dom' import {Simulate} from 'react-dom/test-utils' +import waitForExpect from 'wait-for-expect' import * as queries from './queries' function render(ui, {container = document.createElement('div')} = {}) { @@ -24,4 +25,4 @@ function flushPromises() { return new Promise(resolve => setImmediate(resolve)) } -export {render, flushPromises, Simulate} +export {render, flushPromises, Simulate, waitForExpect} diff --git a/typings/index.d.ts b/typings/index.d.ts index 55c9963c..153836db 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,4 +1,5 @@ import {Simulate as ReactSimulate} from 'react-dom/test-utils' +import waitForExpect from 'wait-for-expect' interface RenderResult { container: HTMLDivElement @@ -21,3 +22,5 @@ export function render( export function flushPromises(): Promise export const Simulate: typeof ReactSimulate + +export function waitForExpect(): typeof waitForExpect