Skip to content

Commit

Permalink
feat(waitForExpect): add waitForExpect (#25)
Browse files Browse the repository at this point in the history
* added waitForExpect with test

* added typescript and simplified version of the waitForExpect, used and exports its typings

* added initial notes about waitForExpect

* fixed styling

* minor stylistic change

* updated tests to remove the nesting

* updated readme

* fixed d .md syntax

* improved style


Closes #21
  • Loading branch information
lgandecki authored and Kent C. Dodds committed Mar 29, 2018
1 parent b13b1c1 commit 4adbc1d
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 4 deletions.
10 changes: 10 additions & 0 deletions .all-contributorsrc
Expand Up @@ -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"
]
}
]
}
66 changes: 64 additions & 2 deletions README.md
Expand Up @@ -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]

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

</details>

<details>

<summary><a name="waitForExpectFAQ"></a>Is there a different way to wait for things to happen? For example for end to end or contract tests?</summary>
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(<ComponentWithLoader />)

// 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.

</details>

## Other Solutions
Expand Down Expand Up @@ -634,7 +696,7 @@ Thanks goes to these people ([emoji key][emojis]):
<!-- prettier-ignore -->
| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub><b>Kent C. Dodds</b></sub>](https://kentcdodds.com)<br />[馃捇](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") | [<img src="https://avatars1.githubusercontent.com/u/2430381?v=4" width="100px;"/><br /><sub><b>Ryan Castner</b></sub>](http://audiolion.github.io)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=audiolion "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/8008023?v=4" width="100px;"/><br /><sub><b>Daniel Sandiego</b></sub>](https://www.dnlsandiego.com)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=dnlsandiego "Code") | [<img src="https://avatars2.githubusercontent.com/u/12592677?v=4" width="100px;"/><br /><sub><b>Pawe艂 Miko艂ajczyk</b></sub>](https://github.com/Miklet)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=Miklet "Code") | [<img src="https://avatars3.githubusercontent.com/u/464978?v=4" width="100px;"/><br /><sub><b>Alejandro 脩谩帽ez Ortiz</b></sub>](http://co.linkedin.com/in/alejandronanez/)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=alejandronanez "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1402095?v=4" width="100px;"/><br /><sub><b>Matt Parrish</b></sub>](https://github.com/pbomb)<br />[馃悰](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") | [<img src="https://avatars1.githubusercontent.com/u/1288694?v=4" width="100px;"/><br /><sub><b>Justin Hall</b></sub>](https://github.com/wKovacs64)<br />[馃摝](#platform-wKovacs64 "Packaging/porting to new platform") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[馃捇](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") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[馃捇](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") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;"/><br /><sub><b>艁ukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[馃捇](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") |

<!-- ALL-CONTRIBUTORS-LIST:END -->

Expand Down
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -25,7 +25,9 @@
"keywords": [],
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
"license": "MIT",
"dependencies": {},
"dependencies": {
"wait-for-expect": "0.4.0"
},
"devDependencies": {
"@types/react-dom": "^16.0.4",
"axios": "^0.18.0",
Expand Down
40 changes: 40 additions & 0 deletions 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 <div>Loading...</div>
} else {
return (
<div data-testid="message">
Loaded this message: {this.state.data.returnedMessage}!
</div>
)
}
}
}

test('it waits for the data to be loaded', async () => {
const {queryByText, queryByTestId} = render(<ComponentWithLoader />)

expect(queryByText('Loading...')).toBeTruthy()

await waitForExpect(() => expect(queryByText('Loading...')).toBeNull())
expect(queryByTestId('message').textContent).toMatch(/Hello World/)
})
3 changes: 2 additions & 1 deletion 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')} = {}) {
Expand All @@ -24,4 +25,4 @@ function flushPromises() {
return new Promise(resolve => setImmediate(resolve))
}

export {render, flushPromises, Simulate}
export {render, flushPromises, Simulate, waitForExpect}
3 changes: 3 additions & 0 deletions 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
Expand All @@ -21,3 +22,5 @@ export function render(
export function flushPromises(): Promise<void>

export const Simulate: typeof ReactSimulate

export function waitForExpect(): typeof waitForExpect

0 comments on commit 4adbc1d

Please sign in to comment.