81

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.

2
  • Hi.. how did you solve this issue? I have same code and needs to test after promise is completed. Commented Feb 26, 2019 at 7:02
  • 2
    FYI, you should not reject a string. You should reject an error, like reject(new Error('error')). Commented Apr 7, 2020 at 19:22

4 Answers 4

81

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');
});
8
  • 7
    This is the official way to check async errors. This should be the accepted answer. Commented Feb 24, 2021 at 9:21
  • 2
    Note: should probably make the method async and add await before expect (see linked official docs)
    – Ahmed-Anas
    Commented Sep 30, 2021 at 6:51
  • 1
    @Ahmed-Anas see the first example in the link. The one that starts with t('tests error with rejects', () => { not the other. Commented Sep 30, 2021 at 12:54
  • 1
    I had to make make a slight variation to make this work: expect(wrapper.instance().handleFormSubmit()).rejects.toEqual(Error('error));
    – jacobytes
    Commented Oct 18, 2021 at 12:53
  • 1
    @killthrush Slight tweak to what you said. One has to await for correction functionality, but need to await the expect-chain, NOT the function inside the expect() argument. Awaiting that promise will, as you say, simply throw if rejected. In that case, one could simply treat the test as the normal expect(() => { /* do something */ }).toThrow().
    – ErikE
    Commented Dec 2, 2023 at 1:27
23

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

2
  • 7
    wouldn't this test give false positive if the function didn't throw?
    – oyalhi
    Commented Feb 14, 2020 at 21:09
  • 6
    @oyalhi no, it won't give a false positive. The line expect.assertions(1) tells jest that there will be an assertion so if the catch isn't triggered, jest will complain about the missing assertion
    – Akhil F
    Commented Feb 23, 2020 at 0:31
4

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

2
  • Note that your test can also confirm that state.loading is still set to true
    – Shiraz
    Commented Jul 20, 2019 at 11:30
  • What if the target code of the test needed to use "new services" thus you needed to do jest.doMock with a requireActual and override just a single function? Commented Jul 3, 2020 at 15:52
0

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

2
  • 3
    this didn't solve the issue. The code still goes into the catch, but the test still does not wait for the promise to fulfill before running the expects
    – coding123
    Commented Jun 13, 2018 at 9:53
  • My bad, Have updated the answer with documentation link. It will surely solve your problem Commented Jun 13, 2018 at 17:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.