Skip to content

Commit

Permalink
Update promise docs (jestjs#3201)
Browse files Browse the repository at this point in the history
* Update docs on promises

* Remove promises from `expect.assertions` and `test`

* Keep docs for promise return

* Update async docs

* Update the async tutorial

* Nicer headlines in Testing Async code

* Update TestingAsyncCode.md

* Update TutorialAsync.md
  • Loading branch information
robinpokorny authored and cpojer committed May 2, 2017
1 parent f46edad commit 7e4a700
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 56 deletions.
52 changes: 28 additions & 24 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,23 @@ describe('Beware of a misunderstanding! A sequence of dice rolls', () => {

`expect.assertions(number)` verifies that a certain number of assertions are called during a test. This is often useful when testing asynchronous code, in order to make sure that assertions in a callback actually got called.

For example, let's say that we have a few functions that all deal with state. `prepareState` calls a callback with a state object, `validateState` runs on that state object, and `waitOnState` returns a promise that waits until all `prepareState` callbacks complete. We can test this with:
For example, let's say that we have a function `doAsync` that receives two callbacks `callback1` and `callback2`, it will asynchronously call both of them in an unknown order. We can test this with:

```js
test('prepareState prepares a valid state', () => {
expect.assertions(1);
prepareState(state => {
expect(validateState(state)).toBeTruthy();
});
return waitOnState();
test('doAsync calls both callbacks', () => {
expect.assertions(2);
function callback1(data) {
expect(data).toBeTruthy();
}
function callback2(data) {
expect(data).toBeTruthy();
}

doAsync(callback1, callback2);
});
```

The `expect.assertions(1)` call ensures that the `prepareState` callback actually gets called.
The `expect.assertions(2)` call ensures that both callbacks actually get called.

### `expect.hasAssertions()`

Expand Down Expand Up @@ -308,51 +312,51 @@ test('the best flavor is not coconut', () => {

##### available in Jest **20.0.0+**

If your code uses Promises, use the `.resolves` keyword, and Jest will wait for the Promise to resolve and then run an assertion on the resulting value.
Use `resolves` to unwrap the value of a fulfilled promise so any other matcher can be chained. If the promise is rejected the assertion fails.

For example, this code tests that the Promise returned by `fetchData()` resolves and that the resulting value is peanut butter:
For example, this code tests that the promise resolves and that the resulting value is `'lemon'`:

```js
test('fetchData() resolves and is peanut butter', () => {
test('resolves to lemon', () => {
// make sure to add a return statement
return expect(fetchData()).resolves.toBe('peanut butter');
return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});
```

Alternatively, you can use `async/await` in combination with `.resolves`:

```js
test('fetchData() resolves and is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
await expect(fetchData()).resolves.not.toBe('coconut');
test('resolves to lemon', async () => {
await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus');
});
```

### `.rejects`

##### available in Jest **20.0.0+**

If your code uses Promises, use the `.rejects` keyword, and Jest will wait for that Promise to reject and then run an assertion on the resulting value.
Use `.rejects` to unwrap the reason of a rejected promise so any other matcher can be chained. If the promise is fulfilled the assertion fails.

For example, this code tests that the Promise returned by `fetchData()` rejects and that the resulting value is an error:
For example, this code tests that the promise rejects with a reason:

```js
test('fetchData() rejects to be error', () => {
// make sure to add a return statement
return expect(fetchData()).rejects.toEqual({
error: 'User not found',
});
return expect(Promise.reject('octopus')).rejects.toBeDefined();
});
```

Alternatively, you can use `async/await` in combination with `.rejects`:
Alternatively, you can use `async/await` in combination with `.rejects`.
Moreover, this code tests that the returned reason includes 'octopus'`:

```js
test('fetchData() rejects to be error', async () => {
await expect(fetchData()).rejects.toEqual({
error: 'User not found',
const drinkOctopus = new Promise(() => {
throw new DisgustingFlavorError('yuck, octopus flavor');
});
await expect(fetchData()).rejects.not.toBe('Mark');

await expect(drinkOctopus).rejects.toMatch('octopus');
});
```

Expand Down
2 changes: 1 addition & 1 deletion docs/GlobalAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ test('did not rain', () => {

The first argument is the test name; the second argument is a function that contains the expectations to test.

To test an asynchronous function, just return a promise from `test`. When running tests, Jest will wait for the promise to resolve before letting the test complete.
If a promise is returned from `test`, Jest will wait for the promise to resolve before letting the test complete.

For example, let's say `fetchBeverageList()` returns a promise that is supposed to resolve to a list that has `lemon` in it. You can test this with:

Expand Down
62 changes: 55 additions & 7 deletions docs/TestingAsyncCode.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ It's common in JavaScript for code to run asynchronously. When you have code tha

The most common asynchronous pattern is callbacks.

For example, let's say that you have a `fetchData(callback)` function that fetches some data and calls `callback(data)` when it is complete. You want to test that this returned data is just the string `"peanut butter"`.
For example, let's say that you have a `fetchData(callback)` function that fetches some data and calls `callback(data)` when it is complete. You want to test that this returned data is just the string `'peanut butter'`.

By default, Jest tests complete once they reach the end of their execution. That means this test will *not* work as intended:

Expand Down Expand Up @@ -50,10 +50,11 @@ If `done()` is never called, the test will fail, which is what you want to happe

If your code uses promises, there is a simpler way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.

For example, let's say that `fetchData`, instead of using a callback, returns a promise that is supposed to resolve to the string `"peanut butter"`. We could test it with:
For example, let's say that `fetchData`, instead of using a callback, returns a promise that is supposed to resolve to the string `'peanut butter'`. We could test it with:

```js
test('the data is peanut butter', () => {
expect.assertions(1);
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
Expand All @@ -62,28 +63,75 @@ test('the data is peanut butter', () => {

Be sure to return the promise - if you omit this `return` statement, your test will complete before `fetchData` completes.

If you expect a promise to be rejected use the `.catch` method. Make sure to add `expect.assertions` to verify that a certain number of assertions are called. Otherwise a fulfilled promise would not fail the test.

```js
test('the fetch fails with an error', async () => {
expect.assertions(1);
return fetchData().catch(e =>
expect(e).toMatch('error')
);
});
```

### `.resolves` / `.rejects`
##### available in Jest **20.0.0+**

You can also use the `resolves` keyword in your expect statement, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.
You can also use the `.resolves` matcher in your expect statement, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.

```js
test('the data is peanut butter', () => {
expect.assertions(1);
return expect(fetchData()).resolves.toBe('peanut butter');
});
```

Be sure to return the promise - if you omit this `return` statement, your test will complete before `fetchData` completes.
Be sure to return the assertion—if you omit this `return` statement, your test will complete before `fetchData` completes.

If you expect a promise to be rejected use the `.rejects` matcher. It works analogically to the `.resolves` matcher. If the promise is fulfilled, the test will automatically fail.

```js
test('the fetch fails with an error', () => {
expect.assertions(1);
return expect(fetchData()).rejects.toMatch('error');
});
```

### Async/Await

If your code uses `async` and `await`, you can use these in your tests as well. To write an async test, just use the `async` keyword in front of the function passed to `test`. For example, the same `fetchData` scenario can be tested with:
Alternatively, you can use `async` and `await` in your tests. To write an async test, just use the `async` keyword in front of the function passed to `test`. For example, the same `fetchData` scenario can be tested with:

```js
test('the data is peanut butter', async () => {
expect.assertions(1);
const data = await fetchData();
expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
```

Of course, you can combine `async` and `await` with `.resolves` or `.rejects` (available in Jest **20.0.0+**).

```js
test('the data is peanut butter', async () => {
expect.assertions(1);
await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
expect.assertions(1);
await expect(fetchData()).rejects.toMatch('error');
});
```

In this case, `async` and `await` are effectively just syntactic sugar for the same logic as the promises example uses.
In these cases, `async` and `await` are effectively just syntactic sugar for the same logic as the promises example uses.

None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style makes your tests the simplest.
None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style makes your tests simpler.
74 changes: 63 additions & 11 deletions docs/TutorialAsync.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,53 @@ export default function request(url) {
}
```

Now let's write a test for our async functionality. Using the `resolves` keyword (available in Jest **20.0.0+**)
Now let's write a test for our async functionality.
```js
// __tests__/user-test.js
jest.mock('../request');

import * as user from '../user';

// The promise that is being tested should be returned.
// The assertion for a promise must be returned.
it('works with promises', () => {
return expect(user.getUserName(5)).resolves.toEqual('Paul');
expect.assertions(1);
return user.getUserName(4).then(data => expect(data).toEqual('Mark'));
});
```

We call `jest.mock('../request')` to tell Jest to use our manual mock. `it` expects the return value to be a Promise that is going to be resolved.
You can chain as many Promises as you like and call `expect` at any time, as
long as you return a Promise at the end.

### `.resolves`
##### available in Jest **20.0.0+**

There is a less verbose way using `resolves` to unwrap the value of a fulfilled promise together with any other matcher. If the promise is rejected, the assertion will fail.

```js
it('works with resolves', () => {
expect.assertions(1);
return expect(user.getUserName(5)).resolves.toEqual('Paul');
});
```

### `async`/`await`

Writing tests using the `async`/`await` syntax is easy. Here is
how you'd write the same example from before:
how you'd write the same examples from before:

```js
// async/await can also be used.
// async/await can be used.
it('works with async/await', async () => {
await expect(user.getUserName(4)).resolves.toEqual('Mark');
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark');
});

// async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(user.getUserName(5)).resolves.toEqual('Paul');
});
```

Expand All @@ -103,18 +124,49 @@ and enable the feature in your `.babelrc` file.

### Error handling

Errors can be handled using the keyword `rejects` in your expect statement. This will verify that the promise rejects and perform an assertion on the resulting error.
Errors can be handled using the `.catch` method. Make sure to add `expect.assertions` to verify that a certain number of assertions are called. Otherwise a fulfilled promise would not fail the test:

```js
// Testing for async errors can be done using `catch`.
it('tests error with promises', () => {
// Testing for async errors using Promise.catch.
test('tests error with promises', async () => {
expect.assertions(1);
return user.getUserName(2).catch(e =>
expect(e).toEqual({
error: 'User with 2 not found.',
})
);
});

// Or using async/await.
it('tests error with async/await', async () => {
expect.assertions(1);
try {
await user.getUserName(1);
} catch (e) {
expect(e).toEqual({
error: 'User with 1 not found.',
});
}
});
```

### `.rejects`
##### available in Jest **20.0.0+**

The`.rejects` helper works like the `.resolves` helper. If the promise is fulfilled, the test will automatically fail.

```js
// Testing for async errors using `.rejects`.
it('tests error with rejects', () => {
expect.assertions(1);
return expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

// Or try-catch.
it('tests error with async/await', async () => {
// Or using async/await with `.rejects`.
it('tests error with async/await and rejects', async () => {
expect.assertions(1);
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
Expand Down
Loading

0 comments on commit 7e4a700

Please sign in to comment.