coding123
coding123

Reputation: 943

How do I properly test for a rejected promise using Jest?

Code

import { createUser } from '../services';
...
...

handleFormSubmit = () => {  
  this.setState({ loading: true });
  createUser()
    .then(() => {
      this.setState({
        loading: false,
      });
    })
    .catch(e => {
       this.setState({
         error: e,
       });
    });
};

Test

 it('rejects...', () => {
    const Container = createUserContainer(CreateUser);
    const wrapper = shallow(<Container />);  

    return wrapper.instance().handleFormSubmit()
      .catch(e => {
         console.log("State: ", wrapper.state());
         expect(e).toEqual('error');
      });
 });

Mock

export const createUser = function() {
  return new Promise((resolve, reject) => {
    reject('error');
  });
};

The test does force the code to go into the catch in the method. So the state does get set to 'error'.

But in my test, it doesn't do what I expect and wait for the Promise to reject before it tests for the state change. I'm not sure what to try here, should I be using async/await?

So it's the createUser method I want to wait for but I'm not sure my implementation allows for this.

Upvotes: 83

Views: 118319

Answers (4)

Andres Gardiol
Andres Gardiol

Reputation: 1447

You should do something like this:

it('rejects...', () => {
  const Container = createUserContainer(CreateUser);
  const wrapper = shallow(<Container />);  
  return expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});

I think it is cleaner this way. You can see this approach in the official docs.

It's important to note that .rejects (and .resolves) returns a promise, which is returned in the example above so that jest knows to wait on it. If you don't return it, you MUST await it:

it('rejects...', async () => {
  const Container = createUserContainer(CreateUser);
  const wrapper = shallow(<Container />); 
  await expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});

Upvotes: 83

Casey Watson
Casey Watson

Reputation: 52682

The test fails because it's not aware that the subject is asynchronous. It can be fixed by using a done param or making the test function async.

Note it's also necessary to set the number of expected assertions so that the test will fail even if the catch branch is not taken.

async/await style:

 it('rejects...', async () => {
    expect.assertions(1);
    const Container = createUserContainer(CreateUser);
    const wrapper = shallow(<Container />); 

    await wrapper.instance().handleFormSubmit()
      .catch(e => {
         console.log("State: ", wrapper.state());
         expect(e).toEqual('error');
      });
 });

Older style done param:

 it('rejects...', done => {
    expect.assertions(1);
    const Container = createUserContainer(CreateUser);
    const wrapper = shallow(<Container />);  

    wrapper.instance().handleFormSubmit()
      .catch(e => {
         console.log("State: ", wrapper.state());
         expect(e).toEqual('error');
         done();
      });
 });

Asynchronous Testing Reference

expect.assertions reference

Upvotes: 23

Shiraz
Shiraz

Reputation: 2820

Your code looks correct. Why do you say that it doesn't wait for the Promise to reject? The only difference I would make would be to make use of Jest's mocking capability, so change

Mock

export const createUser = function() {
  return new Promise((resolve, reject) => {
    reject('error');
  });
};

to

Test

jest.mock('../services');
const services = require('../services');

const createUser = jest.spyOn(services, "createUser");
createUser.mockRejectedValue("error");

...

it('rejects...', () => {

There's no need to have a separate Mock file

Upvotes: 4

Hriday Modi
Hriday Modi

Reputation: 2081

In your code handleFormSubmit function should return Promise on which you can wait in your test. Also you need to return truthful data from success and error callback to resolve and reject the promise respectively.

handleFormSubmit = () => {  
  this.setState({ loading: true });
  return createUser()
    .then(() => {
      this.setState({
        loading: false,
      });
      return true;
    })
    .catch(e => {
       this.setState({
         error: e,
       });
       throw e;
    });
};

Here in your actual code you have caught the error in catch handler and trying to catch it further in out test case code. Hence catch can not be chained further, while you can chain then multiple times.

For reference go through Promise documentations: https://www.peterbe.com/plog/chainable-catches-in-a-promise

Upvotes: 0

Related Questions