0

I am seeing a behavior that I can not understand. The following test fails to capture the thrown error:

await expect(authService.register({ email, password })).rejects.toThrow();

While this one successfully captures the thrown error:

const register = authService.register;
await expect(register({ email, password })).rejects.toThrow();

I am aware this could be a bug, but this literally does not make sense to me, since expect function receives the exact same Promise in both cases, but still behaves differently. How is this behavior even possible?

Here is the register method of AuthService class if that is gonna be of any help:

async register(cmd: RegisterCmd) {
  // check if email already exists
  const password = await this.db.getPassword({ email: cmd.email });
  if (password) {
    throw new ClientError(ErrorKind.BadRequest, 'Email is already registered');
  }
  // hash password
  const hashedPassword = await this.crypt.hash(cmd.password, N);
  // save account to database
  await this.db.createAccount({ email: cmd.email, password: hashedPassword });
}

I have the following mock return value at the beginning of the test to make sure the password is returned, meaning that the user is already registered, hence it should throw an error.

mockDb.getPassword.mockResolvedValueOnce('pass');

db and crypt ports are mocked in the test as following:

beforeAll(() => {
  mockDb = mock<IDatabase>();
  mockCrypt = mock<ICrypt>();
  authService = new AuthService(mockDb, mockCrypt);
});

I am also resetting the mocks here:

afterEach(() => {
  jest.resetAllMocks();
});
  • What does register method do? – Konrad Apr 06 '23 at 23:10
  • @Konrad updated the question to give more context – Ahmet Yazıcı Apr 06 '23 at 23:34
  • Why are you testing whether `rejects` throws, though? If it's a promise, you check whether the argument to `rejects` equals something, there shouldn't ever be a throw because you're not using `await`. So: `expect(somepromise).rejects.toEqual(...)` or `expect(await ...).toThrow()`. – Mike 'Pomax' Kamermans Apr 06 '23 at 23:36
  • @Mike'Pomax'Kamermans no because if I await inside expect, then the value is resolved right away without going into expect, any value resolved goes into the expect function, and if an error thrown, there is no way for expect function to know about it. Here is the jest specification for handling async error test cases https://jestjs.io/docs/asynchronous. – Ahmet Yazıcı Apr 06 '23 at 23:38
  • 1
    Right, but promises don't throw. If you look at my link, the rejection is checked for equality, and if you look at your link, the rejection is matched. Async functions only throw when used with `await`. so if you absolutely need to test whether a throw occurs, you can wrap the call: `expect(async() => await ......).toThrow()`. but you probably don't, checking the argument to `reject` tests the same thing. – Mike 'Pomax' Kamermans Apr 06 '23 at 23:39
  • 1
    That is true. But the Promise is awaited inside the expect function and that is how the expect is able to know whether it threw or not. By specifying `expect(Promise).rejects`, we are saying that, here is an unresolved Promise, await for it, and check whether it throws or not. And this is the way to test it undoubtedly. You can check the docs I sent above, Async/Await title, also you can check the stackoverflow, and this is the way to do it with jest. The actual question is, why #1 does not work and #2 does. – Ahmet Yazıcı Apr 06 '23 at 23:47
  • It throws in the second example because you change `this` context. `this.db` is `undefined` which causes an error - that's my guess. Add some `console.log`s inside the function to see what is the value of password etc – Konrad Apr 06 '23 at 23:50
  • 1
    Nope :( I put console.log inside the `if (password)` statement, right above throw, and it actually prints the password to the console, and says `Received function did not throw` – Ahmet Yazıcı Apr 07 '23 at 00:00
  • I guess you should use `toEqual` and not `toThrow` https://stackoverflow.com/questions/50821835/how-do-i-properly-test-for-a-rejected-promise-using-jest – Konrad Apr 07 '23 at 00:03
  • When I do that, I actually see that error object is captured. But it still does not explain why the #1 is not working, and the #2 is working. `toThrow` is working fine in #2 case, but not in #1, which is very very very very weird imo. – Ahmet Yazıcı Apr 07 '23 at 00:11
  • Would it work if you will throw the error in the first line of the function? – Konrad Apr 07 '23 at 00:19
  • Nope again, then the test suite thinks the error is actually failed since smh. is thrown outside of the expect. – Ahmet Yazıcı Apr 07 '23 at 08:03

0 Answers0