Jest: Testing expected errors without Assertion Plan (misleading docs)

Created on 25 Oct 2016  Â·  7Comments  Â·  Source: facebook/jest

I was trying out Jest and started to write some tests around expected errors.
In the docs I found a section for async testing and error handling: https://facebook.github.io/jest/docs/tutorial-async.html#error-handling

// Testing for async errors can be done using `catch`.
it('tests error with promises', () => {
  return user.getUserName(3)
    .catch(e => expect(e).toEqual({
      error: 'User with 3 not found.',
    }));
});

// Or try-catch.
it('tests error with async/await', async () => {
  try {
    await user.getUserName(2);
  } catch (object) {
    expect(object.error).toEqual('User with 2 not found.');
  }
});

Both of these examples are super misleading, because the test passes in both cases if the function doesn't throw in the first place. E.g. this is a perfectly fine test that Jest doesn't complain about:

it('error handling', () => {
  try {
    console.log('definitely not failing')
  } catch (err) {
    expect(err).toBeTruthy()
  }
})

I couldn't find any hint in the docs whether something like plan(1) does or doesn't exist, but the migration guide for AVA and Tape mentions that it's unsupported. I would've loved to send a PR that improves the docs directly, but without a plan feature I don't see an elegant solution.

This isn't cool:

it('error handling', () => {
  const expect2 = jest.fn(expect)
  try {
    console.log('definitely not failing')
  } catch (err) {
    expect2(err).toBeTruthy()
  }

  expect(expect2.mock.calls.length).toBe(1)
  // fails now
})

This isn't cool either:

it('error handling', () => {
  try {
    console.log('definitely not failing')
    expect(false).toBeTruthy()
    // fails now
  } catch (err) {
    expect(err).toBeTruthy()
  }
})

And neither is this:

it('error handling', () => {
  try {
    console.log('definitely not failing')
  } catch (e) {
    var err = e
  }
  expect(err).toBeTruthy()
  // fails now
})

I'm not sure if it's just that I'm still thinking in terms of tap[e], or if this really is a problem, and how you would solve it elegantly in a Jest style.

All 7 comments

Yep, you are right we should improve this API and that it sucks currently. My hope was that we could make expect(() => {}).toThrow() async, but that's not easy either. I don't really like the "plan" functionality so much but we should potentially consider it – although it would be a pretty big hack into Jasmine. What do you think is best?

cc @dmitriiabramov

Just a quick thought: If .toThrow() would always await the function passed to expect, would that work?

The same problem occurs for event emitters btw.

it('emit event', () => {
  emitter.on('event', (event) => {
    expect(event).to…
  })
})

In case the event is never fired the test still passes. I know that using a spy on the handler could make this work, but I think t.plan would be way easier in most cases.

yes. this was always a very dangerous thing.
what if we introduce async matchers?

expect.async(await stuff).toThrowErrorMatchingSnapshot();

might also be a default behavior for expect to wait if a promise passed.

I think plan does have some interesting use cases that I would love to see in Jest. There's a good article on it in the AVA docs.

In tape it's relatively easy to implement, because all expectations go through the t callback object like this example:

var test = require('tape');

test('timing test', function (t) {
    t.plan(2);

    t.equal(typeof Date.now, 'function');
    var start = Date.now();

    setTimeout(function () {
        t.equal(Date.now() - start, 100);
    }, 100);
});

Could you potentially do something similar in Jest, but using the global Jest object for the plan function like this?

it('does timing stuff', () => {
    Jest.plan(2);

    expect(typeof Date.now).toEqual('function');
    var start = Date.now();

    setTimeout(function () {
        expect(Date.now() - start).toBe(100);
    }, 100);
});

I don't know enough about how Jest is wired up behind the scenes yet to know if this would be practical. I'd love to have a go myself, but I haven't had much time to look at the source yet so might need a pointer on where to start looking.

It would be expect.plan. We already have per-test state, so we can do this.

Anybody up to make a PR?

This will be in the next Jest release as expect.assertions(number).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

StephanBijzitter picture StephanBijzitter  Â·  3Comments

gustavjf picture gustavjf  Â·  3Comments

mmcgahan picture mmcgahan  Â·  3Comments

kgowru picture kgowru  Â·  3Comments

stephenlautier picture stephenlautier  Â·  3Comments