I am trying to use Jest to test js/Node code with callback function but Jest won't mark the test as 'failed' if expect fails in the callback function.
I have user.test.js as:
test('Create local user test#example.com', async () => {
User.createLocal({email: "test#example.com"}, function(err, usr, options){
expect(err).toBeNull();
expect(options).toBeNull(); // this line fails correctly as options is not null
expect(usr).not.toBeNull();
})
});
When I run the test, Jest reports that it has received JestAssertionError: expect(received).toBeNull() and prints contents of the options (all good so far!) but it does not mark the test as failed. Instead Jest reports the test as PASSed:
PASS model/user.test.js (9.005 s)
√ Create local user test#example.com (12 ms)
Test Suites: 1 passed, 1 total
If I raise expect from the main body of the function (i.e., not within the callback function) then Jest correctly marks the test as failed. But if expect fails within the callback then it just stops the test but it does not mark the test as failed.
What do I need to do so Jest marks the test as 'failed' as (I think) it should - even when expect is called within the callback?
Jest does not wait for async code to be completed before finishing tests. See the docs (particularly on callbacks).
You can use done to let Jest know when to stop waiting for a test:
test('Create local user test#example.com', done => { // done added here
User.createLocal({email: "test#example.com"}, function(err, usr, options){
expect(err).toBeNull();
expect(options).toBeNull();
expect(usr).not.toBeNull();
// The test will pass only if `done` is reached without a failed assertion:
done();
})
});
Related
I added a feature to my code, but when I run all the test, all the test runs successfully, but Jest still throws an internal error with this message:
Ran all test suites.
the_path\node_modules\expect\build\index.js:303
throw error;
^
JestAssertionError: expect(received).toBe(expected) // Object.is equality
Expected: "node-user-settings"
Received: "null"
I created another .js file and called the code I wanted to test with Jest and it runs properly.
Now, I don't understand why Jest throws an internal error.
This is the test that generates the error.
test("asynchronously gets a value, using callbacks without optional filename", () => {
settings.getState_c("moduleName", null, null, (err, data) => {
expect(err).toBe(null);
expect(data).toBe("node-user-settings");
});
});
I think you need to tell Jest that you're testing an asynchronous function: https://jestjs.io/docs/asynchronous.
Use done instead of async if your function does not return a Promise, but is asynchronous.
This question already has answers here:
Testing with React's Jest and Enzyme when simulated clicks call a function that calls a promise
(2 answers)
Closed 4 years ago.
I am testing a component that uses an external Api.js file. When testing the component, I'm mocking the function using
import {
apiCall,
} from './Api';
jest.mock('./Api');
I want to test a case where the Api call fails, to check that my component shows the correct error. In order to do that, I'm faking the Api response to be:
apiCall.mockImplementation(
() => Promise.reject({ error: 'This is my error' }),
);
However, when mocking the response in this way, I am not able to make it execute before the test case is evaluated. Is there any way to wait for that response to happen before finishing the test?
I have created a codesandbox as simple as I can showing the issue. Since seems that codesandbox does not allow to use jest.mock, I'm just making the original API call to return Promise.reject.
https://codesandbox.io/s/6y0rn74mzw
The behaviour is simple: A text "No error" and a button, that when clicked it calls to the API method that automatically returns Promise.reject. In the response, we change the text to "There is an error". The first test just looks for the word 'error' to make the test pass and show the full evaluation of the code (the test stops if something fails), and the second test is the test I would expect to pass if the correct order was applied.
What would be the way to assure the proper execution order in the test case?
When it comes to dealing with Promises in jest tests, what I've done is as follows:
it('test script description', (done) => {
apiCall()
.then((response) => {
// Verify the response from the promise
done();
});
});
I'm obviously not saying that this is the best solution (perhaps someone can point you in a better direction) but it's the only one that worked for me!
So the solution is to use setImmediate.
If I change the test case to be:
it("should pass but fails due to execution order", done => {
console.log("-----------");
expect(true).toBe(true);
const wrapper = mount(<App />);
wrapper.find("button").simulate("click");
setImmediate(() => {
const newWrapper = wrapper.update();
expect(newWrapper.html()).toContain("There is an error");
done();
});
console.log("Third");
console.log("-----------");
});
It passes.
A good explanation here: https://github.com/kentcdodds/react-testing-library/issues/11
Jest's docs provides a negative example of what not to do when testing asynchronous code. I implemented it this way:
const expect = require('expect');
function fetchData(cb) {
setTimeout(cb('peanut butter2'), 1500);
}
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
I ran npx jest test.js, and this was the output:
Fabians-MacBook-Pro:playground fabian$ npx jest test.js
FAIL ./test.js
✕ the data is peanut butter (6ms)
● the data is peanut butter
expect(received).toBe(expected)
Expected value to be (using Object.is):
"peanut butter"
Received:
"peanut butter2"
at callback (playground/test.js:9:22)
at fetchData (playground/test.js:4:16)
at Object.<anonymous>.test (playground/test.js:12:5)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 0.866s, estimated 1s
Ran all test suites matching /test.js/i.
I don't understand the results.
Why does it work even though I didn't call done(), as Jest recommends you do for testing asynchronous code? I'm pretty sure setTimeout is asynchronous, because I tested it in a blank test script using console.log() statements, and the second one fired before the first one which was enclosed in a setTimeout function.
Furthermore, the test failed in 0.866s, when my timeout was set to 1500ms. How could Jest have received the incorrect callback data (peanut butter2) when my callback should not even have been called?
Because your test looks like it should be async, but is actually synchronous due to an error in your code.
You've got the following, which looks like it's designed to call the method cb after 1500ms, but you're calling cb immediately:
setTimeout(cb('peanut butter2'), 1500);
Which in turn passes the string into your callback function which runs the expect immediately/synchronously.
What you probably wanted was something like:
setTimeout(function() { cb('peanut butter2') }, 1500);
Or alternatively, to have setTimeout pass the arg into your cb function and call it:
setTimeout(cb, 1500, 'peanut butter2')
Which would actually call your cb function after 1500ms, as expected.
I am trying to understand how the asynchronous code for Mocha (at http://mochajs.org/#getting-started) works.
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(function(err) {
if (err) throw err;
done();
});
});
});
});
I want to know how Mocha decides whether a test has succeeded or failed behind the scenes.
I can understand from the above code that user.save() being asynchronous would return immediately. So Mocha would not decide if the test has succeeded or failed after it executes it(). When user.save() ends up calling done() successfully, that's when Mocha would consider it to be a successful test.
I cannot understand how it Mocha would ever come to know about a test failure in the above case. Say, user.save() calls its callback with the err argument set, then the callback throws an error. None of Mocha's function was called in this case. Then how would Mocha know that an error occurred in the callback?
Mocha is able to detect failures that prevent calling the callback or returning a promise because it uses process.on('uncaughtException', ...); to detect exceptions which are not caught. Since it runs all tests serially, it always knows to which test an uncaught exception belongs. (Sometimes people are confused by this: telling Mocha a test is asynchronous does not mean Mocha will run it in parallel with other tests. It just tells Mocha it should wait for a callback or a promise.)
Unless there is something that intervenes to swallow exceptions, Mocha will know that the test failed and will report the error as soon as it detects it. Here is an illustration. The first test fails due to a generic exception thrown. The 2nd one fails due to an expect check that failed. It also raises an unhandled exception.
var chai = require("chai");
var expect = chai.expect;
it("failing test", function (done) {
setTimeout(function () {
throw new Error("pow!");
done();
}, 1000);
});
it("failing expect", function (done) {
setTimeout(function () {
expect(1).to.equal(2);
done();
}, 1000);
});
This is the output on my console:
1) failing test
2) failing expect
0 passing (2s)
2 failing
1) failing test:
Uncaught Error: pow!
at null._onTimeout (test.js:6:15)
2) failing expect:
Uncaught AssertionError: expected 1 to equal 2
+ expected - actual
-1
+2
at null._onTimeout (test.js:13:22)
The stack traces point to the correct code lines. If the exceptions happened deeper, the stack would be fuller.
When Mocha cannot report what went wrong exactly, that's usually because there is intervening code that swallows the exception that was raised. Or when you use promises the problem may be that someone forgot to call a method that indicates whether the promise is supposed to be completely processed and unhandled exceptions should be thrown. (How you do this depends on the promise implementation you use.)
It won't, it's a shame. It has no way to know that your callback is executing. It's an easier way to do asynchronous testing, where you just tell the test when you are finished. The downside, as you have noticed, is that errors in asynchronous callbacks won't be detected. Nevermind, Mocha hooks to process.on('uncaughtException',...) as mentioned by Louis. Note that if you use done instead of waitsFor and runs in jasmine, then you will have the problem.
Other frameworks like js-test-driver force to you wrap callbacks so the the testing framework can put a try catch around your callbacks (and you don't need to call done). Your test would look like the following:
var AsynchronousTest = AsyncTestCase('User');
AsynchronousTest.prototype.testSave = function(queue) {
queue.call('Saving user', function(callbacks) {
var user = new User('Luna');
user.save(callbacks.add(function(err) {
if (err) throw err;
// Run some asserts
}));
});
};
I've written an intern test that does some xhr calls (login, fetch data) that depend on each other. So, I've nested them, but still hope to be able to use the chai assertion library inside my handlers.
I find the test is not failed properly, it always hangs and finally intern reports:
FAIL: main - MySuite - Make some async requests.. (10012ms)
CancelError: Timeout reached on main - MySuite - Make some async requests..
This is depite having a line of code: assert(false, 'Oh no, something went wrong'); that is executed.
From what I've seen inside the assert library it throws exceptions which are expected to be caught higher up the call stack, but this approach isn't suitable for the call stack of an async request handler.
Can I use assert() style functions at this point in the code, or am I forced to reject the original dfd given me by this.async(timeout)?
This question is different to Async test doesn't error on fail in that he was misusing the original dfd from this.async() .. I am trying to not use that, and instead use the higher level abstrations of the chai assertion library.
My simplified test module:
/*jshint dojo:true */
/*global console:true */
'use strict';
define([
'intern!tdd',
'intern/chai!assert',
'intern/dojo/request'
], function (test, assert, request) {
console.log('Test has started to run.');
var testTimeout = 10000;
test.suite('MySuite', function () {
test.test('Make some async requests..', function () {
var dfd = this.async(testTimeout);
var promise = request('http://dojotoolkit.org/js/dojo/1.8/release/dtk/dijit/themes/claro/claro.css')
.then(function (res) {
console.log('First request OK: ', res.length, ' chars.');
// Make a second request
request('http://dojotoolkit.org/css/print.css')
.then(function (res2) {
console.log('Second request OK: ', res2.length, ' chars.');
// Now pretend we hit an error
console.log('Faking an assert fail...');
assert(false, 'Oh no, something went wrong');
// We would have got here if it weren't for those pesky assertions
dfd.resolve('test passed');
}, function (err) {
// Record the error
console.log('Inner Error handler was hit: ', err);
//Error Callback
//Ensure no HTTP errors raised.
dfd.reject.bind(dfd);
});
},
function (err) {
// Record the error
console.log('Outer Error handler was hit: ', err);
//Error Callback
//Ensure no HTTP errors raised.
dfd.reject.bind(dfd);
});
});
});
});
intern.js:
// Learn more about configuring this file at <https://github.com/theintern/intern/wiki/Configuring-Intern>.
// These default settings work OK for most people. The options that *must* be changed below are the
// packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites.
define([ 'intern/node_modules/dojo/has' ], function (has) {
has.add('dojo-has-api', true);
return {
// Configuration options for the module loader; any AMD configuration options supported by the specified AMD loader
// can be used here
loader: {
// Packages that should be registered with the loader in each testing environment
packages: [
'node',
{ name: 'testing', location: '.' }
]
},
// Non-functional test suite(s) to run in each browser
suites: [ 'testing' /* 'myPackage/tests/foo', 'myPackage/tests/bar' */ ]
}
});
shell output:
neek#alyssa:~/src/WIN/testing$ node node_modules/.bin/intern-client config=intern suites=internpromises
Defaulting to "console" reporter
Test has started to run.
First request OK: 135540 chars.
Second request OK: 135540 chars.
Faking an assert fail...
FAIL: main - MySuite - Make some async requests.. (10009ms)
CancelError: Timeout reached on main - MySuite - Make some async requests..
at Error (<anonymous>)
In asynchronous tests, you need to wrap the callback functions using dfd.rejectOnError(callback) if you just want the test to fail when an assertion fails, or dfd.callback(callback) if you want the test to fail if an assertion fails or succeed when no assertion fails. See the asynchronous testing portion of the documentation for more information.