Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async function not behaving as I expect with Jest

The Javascript Jest testing framework documentation says that I need to add done() in my callback to test an asynchronous function, as otherwise the function will return after the test completes and so the test will fail. I have Jest added to my package.json, and the following two files:

src.js:

function fetchData(cb) {
  setTimeout(cb, 2000, 'peanut butter')
}

module.exports = fetchData

src.test.js:

const fetchData = require('./src')

test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter')
    // no done() method!
  }

  fetchData(callback);
})

I get passing tests with the above code, but I think I should get a failing test since I don't have done() in my test file. Is my fetchData() method not asynchronous?


EDIT: Following Nicholas' answer, I changed the code to this:

src.js:

function fetchData(cb) {
  setTimeout(cb, 2000, 'peanut')
}

module.exports = fetchData

src.test.js:

const fetchData = require('./src')

test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter')
    done()
  }

  fetchData(callback);
})

The test runner should evaluate the expectation/assertion per the Jest documentation and fail (peanut being passed, peanut butter expected), but still shows a passing test.

Also, the Jest documentation says:

If done() is never called, the test will fail, which is what you want to happen

The test passes both with and without the done() method in the callback, and both with and without a correct (peanut butter) argument passed to the callback (i.e. all four variants pass).

like image 402
webstackdev Avatar asked Oct 29 '22 23:10

webstackdev


2 Answers

If you don't pass done as an argument, the test runner considers the code synchronous and doesn't wait for something special to occur - in this case invoking done.

Therefore, the test is marked as succesful before your assertions/expectations get a chance to run.

like image 69
nicholaswmin Avatar answered Nov 01 '22 20:11

nicholaswmin


Your fetchData is asynchronous on its own and Jest has no influence on that. On the other hand, Jest needs to know when a test finishes, which is generally when the test functions exits, but that only covers synchronous code. In your case, when your test function exits, you haven't called any assertions and Jest considers this a success (because there was no failure).

To demonstrate that no assertion was called, I will use expect.assertions(number) to only pass the test if it called exactly number assertions. For example if you set it to 1 in your example:

test('the data is peanut butter', () => {
  // Require exactly 1 assertion to pass the test
  expect.assertions(1)

  function callback(data) {
    expect(data).toBe('peanut butter')
  }

  fetchData(callback)
})

You will see the error message:

● the data is peanut butter

  expect.assertions(1)

  Expected one assertion to be called but received zero assertion calls.

As you can see, no assertion was called. Apart from the demonstration purpose, you could use expect.assertions to make sure your test fails when the assertions weren't called.

For any asynchronous tasks, you need to let Jest know that the test is asynchronous and you need to notify Jest when it finished. One way is with the done callback. When your test function takes one argument (usually called done), Jest will treat it as an asynchronous test and wait until you call the done() callback, or when the timeout threshold is reached (default: 5s). In your edited example your function takes zero arguments, and your call to done() is not Jest's callback.

test('done callback', done => {
  //                  ^^^^ 1 argument
  // Jest waits until this callback is called or the timeout was reached.
  // ...
})

test('no callback argument', () => {
  //                         ^^ no argument
  // Test finishes as soon as the function exits
  // ...
})

This is rarely used anymore, because promises are much more common nowadays and they make this a lot simpler, as you can return the promise and Jest will wait for its completion without any extra setup. Additionally, you can use async and await to make it even nicer, as it's syntactic sugar on top of promises.

See also Testing Asynchronous Code - Promises and Testing Asynchronous Code - Async/Await

like image 34
Michael Jungo Avatar answered Nov 01 '22 19:11

Michael Jungo