简体   繁体   中英

How do I fail a test in Jest when an uncaught promise rejection occurs?

I'm working on adding test coverage to a Node project I'm working on using Jest. The code I'm testing is throwing errors within promises resulting in an UnhandledPromiseRejectionWarning message being logged to the console.

While writing tests, I can pretty easily identify these issues and resolve them, but these warnings aren't actually causing Jest to mark the tests as failed, so our CI won't catch it. I've searched around for any suggestions and haven't found much.

I did find in Node's documentation that you can catch these warnings and handle them...

process.on('unhandledRejection', (error) => {
  throw error; // Or whatever you like...
});

So it seems like it would be pretty straightforward to add this code into my test cases. After all, an Error thrown within the test should cause the test to fail...

describe('...', () => {
  it('...', () => {
    process.on('uncaughtRejection', (error) => {
      throw error;
    });

    // the rest of my test goes here
  });
});

Unfortunately the behavior I'm seeing is that the error does get thrown, but Jest doesn't catch it and fail the test. Instead, Jest crashes with this error and the tests don't continue to run. This isn't really desirable, and seems like incorrect behavior.

Throwing an error outside of the uncaughtRejection handler works as expected: Jest logs the thrown error and fails the test, but doesn't crash. (ie the test watcher keeps watching and running tests)

The way I've approached this is very much tied into the way I write my functions - basically, any function that uses promises should return a promise. This allows whatever code calls that function to handle catching errors in any way it sees fit. Note that this is my approach and I'm not going to claim this is the only way to do things.

For example... Imagine I'm testing this function:

const myFunction = () => {
    return doSomethingWithAPromise()
        .then(() => {
            console.log('no problems!');
            return true;
        });
};

The test will look something like this:

describe('...', () => {
    it('...', () => {
        return myFunction()
            .then((value) => {
                expect(value).toBe(true);
            });
    });
});

Which works great. Now what happens if the promise is rejected? In my test, the rejected promise is passed back to Jest (because I'm returning the result of my function call) and Jest can report on it.

If, instead, your function does not return a promise, you might have to do something like this:

const myOtherFunction = () => {
    doSomethingWithAPromise()
        .then(() => {
            console.log('no problems!');
            return true;
        })
        .catch((err) => {
             // throw the caught error here
             throw err;
        });
};

Unlike the example above, there is no (direct) way for Jest to handle a rejected promise because you're not passing the promise back to Jest. One way to avoid this might be to ensure there is a catch in the function to catch & throw the error, but I haven't tried it and I'm not sure if it would be any more reliable.

module:

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

test:

import { myPromise } from './module';


it('should reject the promise', () => {
  expect.assertions(1);

  const expectedError = new Error('error test');

  myPromise().catch((error) => {
    expect(error).toBe(expectedError);
  });

Include the following content in Jest's setupFiles :

if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
  process.on('unhandledRejection', reason => {
    throw reason
  })
  // Avoid memory leak by adding too many listeners
  process.env.LISTENING_TO_UNHANDLED_REJECTION = true
}

Courtesy of stipsan in https://github.com/facebook/jest/issues/3251#issuecomment-299183885 .

From the node documentation site we can see that The process object is an instance of EventEmitter .
Using the emit function from process we can trigger the errors like uncaughtRejection and uncaughtException programmatically when needed.

  it("should log the error", () => {
        process.emit("unhandledRejection");
        ...
        const loggerInfo = jest.spyOn(logger, "info");
        expect(loggerInfo).toHaveBeenCalled();
    });

Not sure if this helps, but you can also assert for promise rejections as such

index.js

module.exports = () => {
  return Promise.reject('it didnt work');
}

index.spec.js

const thing = require('../src/index');

describe('rejected promise', () => {
  it('should reject with a reason', ()=> {

    return expect(thing()).rejects.toEqual('it didnt work');
   });
});

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