jest: async/await .not.toThrow idiomatic check

To check that async functions are not throwing exceptions I do this:

let allCorrect = true;
try {
  await check();
} catch(err) {
  allCorrect = false;
}
expect(allCorrect).toBe(true);

Is it possible to do something like this?

expect(async () => await check()).not.toThrow()

We came across this with @kentaromiura today.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 102
  • Comments: 53 (15 by maintainers)

Most upvoted comments

I’d like to cast my vote for this api:

expect(promise).toResolve(optionalResolvedValue);
expect(promise).toReject(optionalRejectionValue);

I like it because it’s simple and explicit.

You can just write: await expect(asyncFunc()).rejects.toThrowError(TypeOfError);

Fixed with #3068. This will land in Jest v20.

@alfonsodev Maybe a slightly safer temporary solution would be:

let message;
try {
  await thingYouExpectToFail();
} catch (error) {
  message = error.message;
}
expect(message).toBe('some string'); // Or expect(message).toBeTruthy();

@piuccio Try await-ing your expect, eventually.

await expect(
  Promise.reject({ message: 'not an error' })
).rejects.toThrow(/error/);

None of the suggestions above were working for me so I ended up doing:

expect.assertions(2);
try {
  await mySpecialAsyncFunction();
} catch (e) {
  expect(e.message).toEqual("some message");
  expect(e instanceof MySpecialError).toBeTruthy();
}

@cpojer I gave this a try and it appears to be more work than expected, and a consequent evolution of the way expect is used. If expect.toThrow was made compatible with promises, then people would have to use it this way:

return expect(asyncFunction).toThrow();
// or better
await expect(asyncFunction).toThrow();

But once you make expect.toThrow compatible with promises, you’ll have a hard time justifying not making all matchers compatible with promises. And that’s a lot of work and added complexity when our initial problem was this try+await+catch block. It might be better to add a syncify util to jest that would allow writing such tests:

it('should throw', async () => {
  const syncFunction = await jest.syncify(asyncFunction);

  expect(syncFunction).toThrow();
});

The syncify implementation is trivial:

syncify = async (fn) => {
  try {
    const result = await fn();
    return () => { return result; };
  } catch (e) {
    return () => { throw e; };
  }
};

Let me know if this idea makes sense to you.

I totally agree we should have this.

@Blutude Your async function does return something: it’s a promise resolving to undefined.

await expect(...).resolves.toBeUndefined()

https://github.com/facebook/jest/issues/1377#issuecomment-396232412 https://github.com/facebook/jest/issues/1377#issuecomment-473922360 https://jestjs.io/docs/en/expect#rejects

Personally, it’s not my favorite approach, someone not knowing the expect.sync() function has no way to understand what it does.

I prefer explicit support of promises in expect:

await expect(promise).toReject().toBeInstanceOf(TypeError)

FWIW, I needed to test whether or not async functions threw in jest, so along with the syncify function, I wrote this little helper:

const getAsyncThrowable = fn => async (...args) =>
  await syncify(async () => await fn(...args))

So that I can write tests like:

it('should throw if the parameter is invalid', async () => {
  const myAsyncFunctionThrowable = getAsyncThrowable(myAsyncFunction)
  expect(await myAsyncFunctionThrowable('good')).not.toThrow()
  expect(await myAsyncFunctionThrowable('bad')).toThrow(/invalid/)
})

Any status on this?

no, please don’t. async/await compiles into a promise. This is literally just like the work you did for pit:

expect(() => {
  return new Promise(…);
}).toThrow();

when the function is called and returns a promise and it doesn’t throw, we should wait for the promise to resolve and then make sure it throws.

Since it creates an async function inside of another async function it should properly make the it callback a promise, right?

None of the suggestions above were working for me so I ended up doing:

expect.assertions(2);
try {
  await mySpecialAsyncFunction();
} catch (e) {
  expect(e.message).toEqual("some message");
  expect(e instanceof MySpecialError).toBeTruthy();
}

That does not work because your tests will be green even when no error is thrown (because then the catch won’t be triggered and no expectations will be run. No expectations are also green)

Here is how I would see the ideal expect. Comments are welcome 😃

(I put this in a Gist to avoid cluttering this thread)

/cc @cpojer

Why can’t Jest detect if a promise was returned in a toThrow function and add it to a queue that will block the test from completing until it resolves? Or add a new expectation like toResolve or toReject, this would make it explicit that the expectation should be awaited.

Up until Jest 22 this used to work

expect(Promise.reject({ message: 'not an error' })).rejects.toThrow(/error/);

As of Jest 23+ it now says

    Expected the function to throw an error matching:
      /error/
    But it didn't throw anything.

Is the change intended?

Thanks @SimenB, I didn’t know about this lib, I’m keeping it close in case I need it.

@odelucca For me the best way for now is rejectionOf that I posted in this comment:

// invert fulfillment and rejection: fulfill with with rejected value, reject fulfillment value
const rejectionOf = promise => promise.then(
  value => { throw value },
  reason => reason
);

const e = await rejectionOf(mySpecialAsyncFunction());
expect(e.message).toEqual("some message");
expect(e instanceof MySpecialError).toBeTruthy();

What about await expect(...).not.toThrow() ? Which is what the original question was about. My async function does not return anything so I cannot use await expect(...).resolves...

@LukasBombach isn’t expect.assertions(2) used for this very reason?

another temporary solution i have used jasmine.fail, which looks pretty idiomatic, although its not part of my eslint preset. So I must add // eslint-disable-line

try {
  await something('bad param');
  fail('something did not reject'); // eslint-disable-line
} catch (e) {
  expect(e).toMatchSnapshot();
}

Would you like to weigh in @cpojer on the proposals made thus far to see if one could be implemented?

This is an issue that I keep stumbling on when testing async code, would be great if a solution gets added to jest in the short run!

I really like your proposal for a syncify function @louisremi!

I have simplified it a bit (together with your helper function @cowboy) and came up with:

sync = fn => async (...args) => {
  try {
    const result = await fn(...args);
    return () => { return result; };
  } catch (e) {
    return () => { throw e; };
  }
};

A sync util could then be added to expect. This can then be used as follows:

it('should throw', async () => {
  const syncFunc = expect.sync(asyncFunc);
  expect(await syncFunc('bad')).toThrow();
});

Or in shortened syntax:

it('should throw', async () => {
  expect(await expect.sync(asyncFunc)('bad')).toThrow();
});

What do you guys think?

This proposal is indeed quite nice to test an async function but it requires some boilerplate when all you need to test is a promise.

Currently it’s easy to test whether a promise resolve by simply await-ing for it but it’s harder (or verbose) to test that a promise reject.

This could be mitigated by using a helper function which would swap the resolution/rejection of the promise:

expect(await rejectionOf(promise)).toBeInstanceOf(TypeError)

const returnArg = value => value
const throwArg = value => { throw value }
const rejectionOf = promise => promise.then(throwArg, returnArg)

But it may be better to make expect() return a promise-like value and to add a toResolve() and toReject() matchers/modifiers:

await expect(promise).toResolve().toBe('foo')
await expect(promise).toReject().toBeInstanceOf(TypeError)

I chose to make toResolve() and toReject() functions (unlike .not) because they can be used by themselves.

Small digression: this example also leads me to think that some matchers are missing, e.g. there are currently no ways to test an error without using toThrowError(). I think it would be nice to add a toBeAnError() matcher and make toThrow() chainable:

expect(myFunction).toThrow().toBeAnError(TypeError)
expect(myOtherFunction).toThrow().toMatchObject({ code: 'ENOENT' })

Ahhh! I did not see that! Thank you!

Thanks for coming up with a few proposals, everyone. I added some more thoughts in the PR by @excitement-engineer: https://github.com/facebook/jest/pull/3068#issuecomment-285070025

@excitement-engineer good catch! (pun totally intended)

try {
    await promise;
    throw new Error(`Jest: test did not throw. ${Math.random()}`)
  } catch (e) {
    expect(() => { throw e; }).toThrowErrorMatchingSnapshot();
  }

throwing inside a try block is usually an eslint faux pas, but i think we can make an exception here. Put a nice random string in there so folks can’t accidentally run -u without looking at their snapshots 😄

Maybe I’m taking crazy pills, but this seems to work just fine, no API change needed:

const expectAsyncToThrow = async (promise) => {
  try {
    await promise;
  } catch(e) {
    expect(() => {throw e}).toThrowErrorMatchingSnapshot();
  }
};

test('throws a hello to your mom', async() => {
  await expectAsyncToThrow(Promise.reject(new Error('Hi mom')));
});

It’s 1 line, it’s expressive, it’s useable today. That said, if someone wanted to write a PR…

That’s kinda strange @excitement-engineer to have an expect in another expect. I would prefer @julien-f 's solution, but maybe a compromise is possible like Jasmine’s done.fail() method:

expect.async(promise).toThrow();
expect.async(async () => await something()).toThrow();

I don’t think jest.sincify is a great name. It should live somewhere on expect possibly and be in the jest-matchers package.

let error: Error;
try { await fn(); } catch (e) { error = e; }
expect(error).toEqual(new Error('Error!!'));