Why is my async Jest test not failing when it should? - javascript

I have some asynchronous actions that I need to test with Jest. My test is currently passing when it should fail.
describe('Asynchronous Code', () => {
it('should execute promise', () => {
console.log(1);
someFunctionThatReturnsAPromise()
.then(() => {
console.log(2);
expect(true).toBeFalsy();
console.log(3);
});
console.log(4);
});
});
When I run npm test, I get the following output:
PASS __tests__/Async.test.js
● Console
console.log __tests__/Async.test.js:3
1
console.log static-content-test/react/actions/DashboardActions.test.js:6
2
console.log static-content-test/react/actions/DashboardActions.test.js:10
4
As you can see, the test is passing, but console.log(3) is never executed because true is not falsy, and the expectation fails.
How can I get Jest to recognize my expectations inside async callbacks?

When testing asynchronous code, you need to return the promise from the test. Change the test body to:
return someFunctionThatReturnsAPromise()
.then(() => {
expect(true).toBeFalsy();
});
With that, the test fails as expected:
FAIL __tests__/Async.test.js
● Asynchronous Code › should execute promise
expect(received).toBeFalsy()
Expected value to be falsy, instead received
true
This is the pattern facebook uses for testing async code with jest.

Alternatively, you can follow the done pattern as described here:
it('should execute promise', (done) => {
someFunctionThatReturnsAPromise()
.then(() => {
expect(true).toBeFalsy();
done();
});
});
This will work with Jest, but is more commonly used with Jasmine and Mocha.

Here is the alternate solution.
Jest will be terminated once it reaches the end of context. So you need to return the promise from the callback to tell it to wait for the promise to get resolved and tested.
Lets say there is a promise
const promise=fetch("blah.com/api")
test("should return valid data",()=>{
return expect(promise).resolves.toBeTruthy()
})
.resolves waits for the promise to resolve, and you apply appropriate
matchers as your wish.
And also you can use .rejects when you are checking for the error cases.

Related

Why "return" on a promise-based test with Jest?

This is a Jest test example :
test("Asynchronous code", () => {
return Promise.resolve("data").then(res => {
expect(res).toBe("data");
});
});
Docs for Jest indicate that we have to return a Promise in a test, because without return:
Your test will complete before the promise returned [...] and then() has a chance to execute the callback.
So if I don't return, expect(res).toBe("data") will never be executed.
But I tested, and it worked the same with or without return (with .resolves and .rejects as well).
To be sure, I write a test without return supposed to failed :
test("Asynchronous code", () => {
Promise.resolve("data").then(res => {
expect(res).toBe("a failure");
});
});
If expect(res).toBe("data") is never executed, the test will pass: return is required.
If the assertion is well tested, the test failed: what do we need return for?
FAIL Cours-Jest/--test--/test.js
● Asynchronous code
expect(received).toBe(expected) // Object.is equality
Expected: "a failure"
Received: "data"
So the assertion is correctly executed. Why do we have to return? What am I missing?
From the doc:
Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will fail.
So when you do something asynchronously and don't return the promise, two things can go wrong:
When your promise actually does something asynchronously that takes longer than the test timeout ... your .then() will not fire at all. There may even be no timeout, as your test returns immediately.
When your promise is rejected, the error is ignored.
A better way to test promises:
test("Asynchronous code", async () => {
const res = await Promise.resolve("data");
expect(res).toBe("data");
});

Force mocha to require a return value?

Is there are way to force mocha to require a return value from tests? When testing Promises i do it like:
it("some test", () => {
return someFunctionThatReturnsAPromise()
.then(result => {
assert.ok(result)
// ...
})
})
and everything passes/fails as it should whether or not the Promise is resolved or rejected. But if I forget to the "return" (which I do A LOT), then the test passes even if the Promise is rejected.
NOTE: I'm using npm-current mocha with chai-assert.
Convert your test into an async test:
it("some test", async () => {
asset.ok(await someFunctionThatReturnsAPromise())
})
I was able to fix this by setting mocha's asyncOnly option to true (https://mochajs.org/api/mocha).
The unfortunate side effect of this is that ALL tests now must return a Promise (or call done()). That is preferable to me though... So far only one of my test modules (*.test.js) wasn't doing this, but I can just return Promise.resolve() from those for now, until I figure out how to change mocha.asyncOnly on a per-module basis.

Why do I keep facing a Mocha "timeout error"; Also node keeps telling me to resolve my promise?

I keep getting a timeout error, it keeps telling me to enusre I have called done(), even though I have.
const mocha = require('mocha');
const assert = require('assert');
const Student = require('../models/student.js');
describe('CRUD Tests',function(){
it('Create Record',function(done){
var s = new Student({
name: "Yash"
});
s.save().then(function(){
assert(s.isNew === false);
done();
});
});
});
Result is -
CRUD Tests
1) Create Record
0 passing (2s) 1 failing
1) CRUD Tests
Create Record:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it
resolves.
(/home/yash/Documents/Development/Node/MongoCRUD/test/CRUD_test.js)
Note that as written, your unit test ignores the fact that save() might reject instead of resolving. Whenever you use this done construct, make sure your unit test handles an error scenario, like this:
s.save().then(function() {
assert(s.isNew === false);
done();
}).catch(error => {
done(error);
});
Alternatively, since Mocha has built-in support for promises, you can remove the done parameter and return the promise directly, like this:
it('Create Record', function() {
// ...
return s.save().then(function() {
assert(s.isNew === false);
});
});
The advantage with this approach is a reject promise will automatically fail the test, and you don't need any done() calls.
I guess that your mocha runs without being connected to your database. So .save() is waiting for a connection which it never get and your mocha timeout.
You can initialize your software system before to run any Mocha test.
For example, connect a database.
// ROOT HOOK Executed before the test run
before(async () => {
// connect to the database here
});
// ROOT HOOK Excuted after every tests finished
after(async () => {
// Disconnect from the database here
});

Unit testing NodeJS Promise inside a function

Im trying to unit test a function that calls a promise...
Using Mocha, Sinon. I have a functional block like this:
myfile.js:
let OuterDependecy = require('mydep');
function TestFunction(callback) {
OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => {callback(err)});
inside my test i have used proxyquire to mock the outerdependecy
testfile.js
let proxyquire = require('proxyquire');
let OuterDepStub = {};
let testingFunc = proxyquire('myfile.js', {'mydep': OuterDepStub});
... then inside my testing block
let stubCallback = function() {
console.log('Stub dubadub dub'); //note...i can use sinon.spy here instead
};
beforeEach(()=>{
OuterDependency.PromiseFunction = function(arg) {
return new Promise((resolve, reject)=>{
reject('BAD');
});
};
spy = sinon.spy(stubCallback);
});
my actual test now calls the main "testfunction"
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback);
expect(spy).to.have.been.called;
done();
});
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
I believe this is probably due to a race condition of sorts where the promise is resolving after my test is run... is there anyway to delay the test until my promise is resolve.
i know in in angularjs promise testing, there was a way to "tick" the promise so it resolves when you want to. possible in nodejs?
is there anyway to delay the test until my promise is resolve.
As far as I understand your issue, yes, you should only call done() after the promise is settled. In order to do that,you need two things:
1- Enforce TestFunction to return a Promise, so you can wait until it resolves:
function TestFunction(callback) {
return OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => { callback(err) });
}
2- Wait to that promise to settle, then call done.
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback).then(() => {
expect(spy).to.have.been.called;
done();
})
});
now, our then block won't run until the catch block within TestFunction, so if the test works as expected (i.e. the catch block fires and the callback gets fired), the expectation and the done calls will always fire after the callback gets called.
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
That's because your expectation runs right after the TestFunction calls, without waiting for it to settle. However, it will get called lately, thus the console.log appears in the next spec.

How to test promises with Mocha

I'm using Mocha to test an asynchronous function that returns a promise.
What's the best way to test that the promise resolves to the correct value?
Mocha has built-in Promise support as of version 1.18.0 (March 2014). You can return a promise from a test case, and Mocha will wait for it:
it('does something asynchronous', function() { // note: no `done` argument
return getSomePromise().then(function(value) {
expect(value).to.equal('foo');
});
});
Don't forget the return keyword on the second line. If you accidentally omit it, Mocha will assume your test is synchronous, and it won't wait for the .then function, so your test will always pass even when the assertion fails.
If this gets too repetitive, you may want to use the chai-as-promised library, which gives you an eventually property to test promises more easily:
it('does something asynchronous', function() {
return expect(getSomePromise()).to.eventually.equal('foo');
});
it('fails asynchronously', function() {
return expect(getAnotherPromise()).to.be.rejectedWith(Error, /some message/);
});
Again, don't forget the return keyword!
Then 'returns' a promise which can be used to handle the error. Most libraries support a method called done which will make sure any un-handled errors are thrown.
it('does something asynchronous', function (done) {
getSomePromise()
.then(function (value) {
value.should.equal('foo')
})
.done(() => done(), done);
});
You can also use something like mocha-as-promised (there are similar libraries for other test frameworks). If you're running server side:
npm install mocha-as-promised
Then at the start of your script:
require("mocha-as-promised")();
If you're running client side:
<script src="mocha-as-promised.js"></script>
Then inside your tests you can just return the promise:
it('does something asynchronous', function () {
return getSomePromise()
.then(function (value) {
value.should.equal('foo')
});
});
Or in coffee-script (as per your original example)
it 'does something asynchronous', () ->
getSomePromise().then (value) =>
value.should.equal 'foo'

Categories