Skip to content

Commit

Permalink
fix(waitForExpect): rename to wait
Browse files Browse the repository at this point in the history
We avoid a major version bump by continuing to export `waitForExpect` as
well.
  • Loading branch information
Kent C. Dodds committed Mar 29, 2018
1 parent 4adbc1d commit 69a395f
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 108 deletions.
169 changes: 66 additions & 103 deletions README.md
Expand Up @@ -76,17 +76,18 @@ facilitate testing implementation details). Read more about this in

* [Installation](#installation)
* [Usage](#usage)
* [`Simulate`](#simulate)
* [`flushPromises`](#flushpromises)
* [`waitForExpect`](#waitforexpect)
* [`render`](#render)
* [`Simulate`](#simulate)
* [`wait`](#wait)
* [Custom Jest Matchers](#custom-jest-matchers)
* [`toBeInTheDOM`](#tobeinthedom)
* [`toHaveTextContent`](#tohavetextcontent)
* [`TextMatch`](#textmatch)
* [`query` APIs](#query-apis)
* [Examples](#examples)
* [FAQ](#faq)
* [Deprecated APIs](#deprecated-apis)
* [`flushPromises`](#flushpromises)
* [Other Solutions](#other-solutions)
* [Guiding Principles](#guiding-principles)
* [Contributors](#contributors)
Expand All @@ -110,8 +111,9 @@ This library has a `peerDependencies` listing for `react-dom`.
```javascript
// __tests__/fetch.js
import React from 'react'
import {render, Simulate, flushPromises} from 'react-testing-library'
import axiosMock from 'axios'
import {render, Simulate, wait} from 'react-testing-library'
import 'react-testing-library/extend-expect' // this adds custom expect matchers
import axiosMock from 'axios' // the mock lives in a __mocks__ directory
import Fetch from '../fetch' // see the tests for a full implementation

test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => {
Expand All @@ -128,61 +130,18 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
Simulate.click(getByText('Load Greeting'))

// let's wait for our mocked `get` request promise to resolve
await flushPromises()
// wait will wait until the callback doesn't throw an error
await wait(() => getByTestId('greeting-text'))

// Assert
expect(axiosMock.get).toHaveBeenCalledTimes(1)
expect(axiosMock.get).toHaveBeenCalledWith(url)
expect(getByTestId('greeting-text').textContent).toBe('hello there')
expect(getByTestId('greeting-text')).toHaveTextContent('hello there')
// snapshots work great with regular DOM nodes!
expect(container.firstChild).toMatchSnapshot()
})
```

### `Simulate`

This is simply a re-export from the `Simulate` utility from
`react-dom/test-utils`. See [the docs](https://reactjs.org/docs/test-utils.html#simulate).

### `flushPromises`

This is a simple utility that's useful for when your component is doing some
async work that you've mocked out, but you still need to wait until the next
tick of the event loop before you can continue your assertions. It simply
returns a promise that resolves in a `setImmediate`. Especially useful when
you make your test function an `async` function and use
`await flushPromises()`.

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 @@ -283,6 +242,44 @@ const usernameInputElement = getByTestId('username-input')
> Learn more about `data-testid`s from the blog post
> ["Making your UI tests resilient to change"][data-testid-blog-post]
### `Simulate`

This is simply a re-export from the `Simulate` utility from
`react-dom/test-utils`. See [the docs](https://reactjs.org/docs/test-utils.html#simulate).

### `wait`

Defined as:

```typescript
function wait(
callback?: () => void,
options?: {
timeout?: number
interval?: number
},
): Promise<void>
```

When in need to wait for non-deterministic periods of time you can use `wait`,
to wait for your expectations to pass. The `wait` function is a small wrapper
around the
[`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) module.
Here's a simple example:

```javascript
// ...
// wait until the callback does not throw an error. In this case, that means
// it'll wait until we can get a form control with a label that matches "username"
await wait(() => getByLabelText('username'))
getByLabelText('username').value = 'chucknorris'
// ...
```

This can be useful when (for example) you integration test your apollo-connected
react components that go a couple level deep, with queries fired up in
consequent components.

## Custom Jest Matchers

There are two simple API which extend the `expect` API of jest for making assertions easier.
Expand Down Expand Up @@ -600,60 +597,26 @@ react components.

</details>

<details>

<summary>How does flushPromises work and why would I need it?</summary>

As mentioned [before](#flushpromises), `flushPromises` uses
[`setImmediate`][set-immediate] to schedule resolving a promise after any pending
tasks in
[the message queue](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop)
are processed. This includes any promises fired before in your test.
## Deprecated APIs

If there are promise callbacks already in JavaScript's message queue pending to be
processed at the time `flushPromises` is called, then these will be processed before
the promise returned by `flushPromises` is resolved. So when you
`await flushPromises()` the code immediately after it is guaranteed to occur after
all the side effects of your async requests have ocurred. This includes any data
your test components might have requested.

This is useful for instance, if your components perform any data requests and update
their state with the results when the request is resolved. It's important to note
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/)
})
```
### `flushPromises`

For consistency and making your tests easier to understand, you can use it instead of flushPromises.
> This API was deprecated in favor of [`wait`](#wait). We try to avoid having
> two ways to do the same thing and you can accomplish everything with `wait`
> that you could with `flushPromises`. A big advantage of `wait`, 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. You can accomplish the exact same
> behavior with `wait` as you had with `flushPromises` by calling `wait` with
> no arguments: `await wait()`

</details>
This is a simple utility that's useful for when your component is doing some
async work that you've mocked out, but you still need to wait until the next
tick of the event loop before you can continue your assertions. It simply
returns a promise that resolves in a `setImmediate`. Especially useful when
you make your test function an `async` function and use
`await flushPromises()`.

## Other Solutions

Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/deprecated.js
@@ -0,0 +1,14 @@
import {flushPromises, waitForExpect} from '../'

test('flushPromises (DEPRECATED) still works', async () => {
const fn = jest.fn()
Promise.resolve().then(fn)
await flushPromises()
expect(fn).toHaveBeenCalledTimes(1)
})

test('waitForExpect (DEPRECATED) still works', async () => {
const fn = jest.fn()
Promise.resolve().then(fn)
await waitForExpect(() => expect(fn).toHaveBeenCalledTimes(1))
})
4 changes: 2 additions & 2 deletions src/__tests__/end-to-end.js
@@ -1,5 +1,5 @@
import React from 'react'
import {render, waitForExpect} from '../'
import {render, wait} from '../'

const fetchAMessage = () =>
new Promise(resolve => {
Expand Down Expand Up @@ -35,6 +35,6 @@ test('it waits for the data to be loaded', async () => {

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

await waitForExpect(() => expect(queryByText('Loading...')).toBeNull())
await wait(() => expect(queryByText('Loading...')).toBeNull())
expect(queryByTestId('message').textContent).toMatch(/Hello World/)
})
4 changes: 2 additions & 2 deletions src/__tests__/fetch.js
@@ -1,6 +1,6 @@
import React from 'react'
import axiosMock from 'axios'
import {render, Simulate, flushPromises} from '../'
import {render, Simulate, wait} from '../'

// instead of importing it, we'll define it inline here
// import Fetch from '../fetch'
Expand Down Expand Up @@ -40,7 +40,7 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
// Act
Simulate.click(getByText('Fetch'))

await flushPromises()
await wait()

// Assert
expect(axiosMock.get).toHaveBeenCalledTimes(1)
Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Expand Up @@ -25,4 +25,8 @@ function flushPromises() {
return new Promise(resolve => setImmediate(resolve))
}

export {render, flushPromises, Simulate, waitForExpect}
function wait(callback = () => {}, {timeout, interval} = {}) {
return waitForExpect(callback, timeout, interval)
}

export {render, flushPromises, Simulate, wait, waitForExpect}
8 changes: 8 additions & 0 deletions typings/index.d.ts
Expand Up @@ -24,3 +24,11 @@ export function flushPromises(): Promise<void>
export const Simulate: typeof ReactSimulate

export function waitForExpect(): typeof waitForExpect

export default function wait(
callback?: () => void,
options?: {
timeout?: number
interval?: number
},
): Promise<void>

0 comments on commit 69a395f

Please sign in to comment.