简体   繁体   中英

How to test that an error is re-thrown in a catch statement with Jest

I have a function that returns a promise, but I want to test that it has a catch defined, and then additionally test that it re-throws the error.

This is a very contrived example but it was the clearest way to show the issue. In my actual code, I am calling a function that is mocked to fail (vs the manually rejecting in this example), and I have additional logging in the catch statement, which explains the re-throwing of the error.

const foo = () => {
  return new Promise((resolve, reject) => {
    reject(new Error('reject')); // manually rejecting to mimic actual code...
  }).catch(error => {
    // do some additional logging...
    throw error;
  });
};

it('should catch and re-throw error', () => {
  // Received function did not throw
  // and
  // Unhandled promise rejection
  expect(() => foo()).toThrow();

  // Test passes, even when `throw error` is commented out with false positive
  expect(foo()).rejects.toThrow();
});

I can successfully check that the logging function is called, but can't figure out how to ensure the error is re-thrown after.

WORKING UPDATE:)

thanks to @skyboyer & @Bergi for getting me to think about the issue a bit differently, and exposing me to some of the finer points of jest

Below is both the updated code to show the logging function, and the updated tests i settled on.

The issues that led to this were

  • unable to test logging was called due to the error being re-thrown
  • unable to test the value of the error being re-thrown

Catching the rejected promise allowed me to do both.

I was going to leave in the rejects.toEqual test, but it seems redundant now...

interested in any feedback! and thanks again!

// myModule.js
export const logging = () => {};
export const bar = () => new Promise(resolve => {});
export const foo = () => {
  return bar().catch(error => {
    logging();
    throw error;
  });
};

describe('myModule', () => {
  let fooReturn;

  beforeEach(() => {
    jest.clearAllMocks();

    jest.spyOn(myModule, 'bar').mockImplementation(() => {
      return Promise.reject({ error: 'bar error' });
    });

    jest.spyOn(myModule, 'logging').mockImplementation(() => {});

    fooReturn = myModule.foo();
  });

  it('should catch and re-throw error', () => {
    expect.assertions(1);
    fooReturn.catch(result => expect(result).toEqual({ error: 'bar error' }));

    // removed since the above test covers that the promise was rejected
    // return fooReturn.rejects.toEqual(expect.anything());
  });

  it('should call the loggin method', async () => {
    expect.assertions(1);

    // prevents UnhandledPromiseRejectionWarning
    fooReturn.catch(() => {});

    expect(myModule.logging).toBeCalled();
  });
});

You missed return .

https://jestjs.io/docs/asynchronous#resolves--rejects

Be sure to return the assertion—if you omit this return statement, your test will complete before the promise returned from fetchData is resolved and then() has a chance to execute the callback.

Your test should be

it('should catch and re-throw error', () => {
  return expect(foo()).rejects.toEqual(expect.anything());
});

As u/Bergi noticed with async/await it may look more laconic:

it('should catch and re-throw error', async () => {
  await expect(foo()).rejects.toEqual(expect.anything());
});

but if we miss to add await before our expect we will have exact the same issue as in version 1 without return . So beware.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM