36

How do you concisely test if a void async function executed successfully with jest? I'm using TypeScript.

// foo.ts
export class Foo {
  public async bar(): Promise<void> {
    await someAsync();
  }
}

How to test that new Foo().bar() does not throw an error?

frederj
  • 1,483
  • 9
  • 20
adanilev
  • 3,008
  • 3
  • 15
  • 20

4 Answers4

75

This is the most semantic way I've found. It is even just a translation of the test name.

describe("Foo.bar()", () => {
      test("Should not throw", async () => {
        await expect(new Foo().bar()).resolves.not.toThrow();
      });
    });
Mr Menezes
  • 874
  • 7
  • 4
  • 1
    Best answer by far – Marcelo Cardoso Dec 11 '19 at 20:57
  • 3
    For those who, like me, missed it, the `await` is very important. If you omit it then the test will always pass. While I don't have another option to hand, I would prefer an option where that sort of mistake isn't syntactically possible. – Toby Smith Dec 31 '20 at 14:38
  • @TobySmith Best solution I've found is a linter that flags unhandled promises (i.e. missing await/void), such as eslint. – Xunnamius Apr 14 '21 at 04:21
  • To the future reader: if you try to convert `.rejects.throw` to `.resolves.not.toThrow` then be very careful; the former needs a function and the latter needs a promise. So `await expect(() => Promise.reject()).rejects.toThrow();` and `await expect(Promise.resolve()).resolves.not.toThrow();` – meakgoz Nov 09 '22 at 09:34
  • @TobySmith https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-query.md – tokland Aug 05 '23 at 13:05
22

The Promise has the advantage that it should not throw at all, rather be resolved or rejected. On the other hand the toBe() assertion expects an argument, even though there is a Promise<void> in TypeScript.

So what if there is no argument passed (or the argument is void) but it is still evaluated. Then the argument is undefined.

  test("Should resolve", async () => {
      await expect(new Foo().bar()).resolves.toBeUndefined();
  });

Testing for not.toThrow() happend to be a false friend for me, because my Foo.bar() did not throw, nor was it resolved either.

Alex
  • 5,240
  • 1
  • 31
  • 38
  • 3
    You can use .toBeUndefined() matcher to test for undefined – Michal Svorc Feb 10 '21 at 10:17
  • 1
    This seems to be the most up to date answer. My usage: `expect(fs.access(path, mode.R_OK)).resolves.toBeUndefined()` – Xunnamius Apr 14 '21 at 04:24
  • ad. "nor was it resolved either" - documentation clearly says `Use resolves to unwrap the value of a fulfilled promise ... If the promise is rejected the assertion fails.` so if your promise from `bar()` was not resolved, it should fail even before it asserts `toThrow()`. Promise has three states pending, fulfilled and rejected. By using `await` Promise is either fulfilled or rejected. Can you please explain, how you end up with unresolved Promise, yet not failed assertion that uses `.resolves.not.toThrow()`? – Risinek Feb 03 '23 at 10:55
  • [jest 'resolves' doc](https://jestjs.io/docs/expect#resolves), [mdn Promise doc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) – Risinek Feb 03 '23 at 10:57
3

This is the best way that I've found. Hoping there's something more elegant.

describe("Foo.bar()", () => {
  it("should not throw", async () => {
    expect.assertions(1);

    try {
      await new Foo().bar();
      expect(true).toBeTruthy();
    } catch {
      // should not come here
    }
  });
});
adanilev
  • 3,008
  • 3
  • 15
  • 20
0

...alternative is resolves assertion.

describe("Foo.bar()", () => {
  it("should not throw", () => {
      return expect(new Foo().bar()).resolves.toEqual();
  });
});

Here you have to return result since it's a Promise(and make jest wait until it's fulfilled). It's slightly more laconic if you need just verify resolved(or rejected - there is similar prop rejects for checking rejection value).

But in case you need to run several checks after promise-based function is run like

describe("Foo.bar()", () => {
  it("should not throw", () => {
      const promise = new Foo().bar()
      expect(promise).resolves.toEqual();
      expect(someMock).toHaveBeenCalled();
      return promise;
  });
});

you may find option with async/await is more... satisfying?

PS as for you variant

describe("Foo.bar()", () => {
  it("should not throw", async () => {
    expect.assertions(1);

    try {
      await new Foo().bar();
      expect(true).toBeTruthy();
    } catch {
      // should not come here
    }
  });
});

I believe it's not needed to catch an error - so expect.assertions also becomes redundant. why? uncaught exception will fail your test and it's expected no exception so it's fine to fail it. such a structure will be needed if you expect exception and want to check its properties.

Also if test fails option with expect.assertions will notify you just about it's failed while uncaught exception will highlight specific statement(useful if test has several exception-possible statements)

[UPD] also I missed initial point that you need to check if promise is resolved with any result(my version check it resolves with undefined that is not really good move(it does not ruin anything if function starts returning something if it returned nothing before). Meanwhile we should have at least one check in test...

So maybe your approach with stub expect(true) is the same legit here.

But I'd verify twice if you don't want to make more expect in the same test case. is that statement under test really such isolated? or you just explode related checks into separate it()?

skyboyer
  • 22,209
  • 7
  • 57
  • 64