I am fairly new to using mocha and I ran into this bug. Please help me understand this behavior.
'use strict';
var flightMiddlewareMock = require(process.cwd() + '/test/mock/response/flightmock');
describe('Test Flights function: getTime', function(){
var mockData;
beforeEach(function(done){
mockData = flightMiddlewareMock.getValidData();
done();
}
it('getFlightDescription returns the full flight description', function(done){
contentUtils.loadContent({
'files': {
activity: '/flights/america'
},
'locality': "en_US"
}, function(err, bundle) {
var flightsMiddleware = new FlightsMiddleware(country, mockData.req, bundle);
console.log('inside content callback');
description = flightsMiddleware.getFlightDescription(mockData.results.body.items[0]);
assert.equal(description, "Boeing 777");
done();
}
});
});
Output looks like this
inside content callback
inside content callback
inside content callback
- Failure of test!
Question - I do not understand why in spite of using 'use strict' it does not complain about description not being declared.
Please note: If I do modify this to
var description = .....
it works viola! Am I missing something?
Thanks for your time!
Mocha will report the exception caused by trying to assign to an undeclared variable. If I run this:
"use strict";
it("foo", function (done) {
setTimeout(function () {
description = "foo";
done();
}, 1000);
});
I get the result:
1) foo
0 passing (1s)
1 failing
1) foo:
Uncaught ReferenceError: description is not defined
at null._onTimeout (test.js:5:21)
Now, I've used setTimeout which is a well-behaved function in that when the callback passed to it throws an exception, setTimeout does not prevent this exception from reaching the top of execution context. In other words, it does not swallow exceptions.
If you have a callback that throws an exception but this exception is swallowed by the code that called the callback, you'll get a test timeout but you won't know why because Mocha won't be able to detect the exception. It relies on the uncaughtException event, which is not emitted if the exception is swallowed.
Related
I have a method that checks whether or not a global function is defined (it may or may not be available, depends on each client's request). If it is defined, it will call it with the appropriate data. If not, it will fail silently. That's the desired behavior.
What I want to do is test it. Is there a way to mock and/or spy on libFunction so that I can ensure it's being called once with the correct data (the function here is very much simplified, there's some data processing that happens along the way).
Here's the method in question:
function sendData(data) {
let exists;
try {
// eslint-disable-next-line no-undef
if (libFunction) exists = true;
} catch (e) {
exists = false;
}
if (exists) {
// eslint-disable-next-line no-undef
libFunction(data);
}
}
I've tried defining libFunction in my tests and then stubbing that, but that doesn't do what I want:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var stub = sinon.stub(libFunction);
var data = "testing";
sendEvent(data);
expect(stub.called).to.be.true;
});
});
This test does not pass, however: AssertionError: expected undefined to be true
I've tried something similar with a spy:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var spy = sinon.spy(libFunction);
var data = "testing";
sendEvent(data);
expect(spy.called).to.be.true;
});
});
This also fails: AssertionError: expected false to be true
Is there a way to do this?
FWIW, I came across this question while trying to solve an issue for stubbing a global method in Node. In my case, this worked (my example uses Sinon.sandbox, but "regular" Sinon.spy should work, as well):
const encodeSpy = sandbox.spy(global, "encodeURIComponent");
// later...
Sinon.assert.calledWith(encodeSpy, {expectedParamValue});
I'm trying to write a plugin for Jasmine that allows you to return a promise from a spec and will pass or fail that spec depending on whether or not the promise is fulfilled or rejected.
Of course, I want to write tests to make sure that my plugin works correctly, and to be thorough, I need to make sure that tests fail when the promise is rejected... so how do I make a test pass when I need to make sure that a test "successfully fails"?
After a conversation with the developers who work on Jasmine, we've come up with this:
var FAILED = 'failed'
var PASSED = 'passed'
describe('My Test Suite', function () {
var env
beforeEach(function () {
// Create a secondary Jasmine environment to run your sub-specs in
env = new jasmine.Env()
})
it('should work synchronously', function () {
var spec
// use the methods on `env` rather than the global ones for sub-specs
// (describe, it, expect, beforeEach, etc)
env.describe('faux suite', function () {
spec = env.it('faux test', function (done) {
env.expect(true).toBe(true)
})
})
// this will fire off the specs in the secondary environment
env.execute()
// put your expectations here if the sub-spec is synchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
})
// don't forget the `done` argument for asynchronous specs
it('should work asynchronously', function (done) {
var spec
// use the methods on `env` rather than the global ones.
env.describe('faux suite', function () {
// `it` returns a spec object that we can use later
spec = env.it('faux test', function (done) {
Promise.reject("FAIL").then(done)
})
})
// this allows us to run code after we know the spec has finished
env.addReporter({jasmineDone: function() {
// put your expectations in here if the sub-spec is asynchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
// this is how Jasmine knows you've completed something asynchronous
// you need to add it as an argument to the main `it` call above
done()
}})
// this will fire off the specs in the secondary environment
env.execute()
})
})
Going off Joe's answer, I moved the fake test context into a single function. Since the code under test is making use of jasmine expectations, I load the inner Env into jasmine.currentEnv_ and call it explicitly with jasmine.currentEnv_.expect(). Note that currentEnv_ is an internal variable set by jasmine itself, so I can't guarantee that this won't be broken in a future jasmine version.
function internalTest(testFunc) {
var outerEnvironment = jasmine.currentEnv_;
var env = new jasmine.Env();
jasmine.currentEnv_ = env;
var spec;
env.describe("fake suite", function () {
spec = env.it("fake test", function () {
func();
});
});
env.execute();
jasmine.currentEnv_ = outerEnvironment;
return spec.result;
}
Then each test looks like
it("does something", function () {
//Arrange
//Act
var result = internalTest(function () {
//Perform action
});
//Assert
expect(result.status).toBe("failed"); //Or "success"
expect(result.failedExpectations.length).toBe(1);
expect(result.failedExpectations[0].message).toBe("My expected error message");
});
I have some issue with calling function of null variable in 'then' callback of Q.promise.
The first call (without Q using) will show an error, but while the second (wuth Q using) doesn't.
Small example:
var Q = require('q');
var nul = null;
var exp;
(function (exp) {
var A = (function () {
function A() {
};
A.prototype.foo = function () {
var d = Q.defer();
d.resolve('Hello, world');
return d.promise;
};
A.prototype.bar = function (i) {
switch (i) {
case 0:
/**
* That's all ok, "TypeError: Cannot read property 'qqq' of null"
*/
console.log(nul);
nul.qqq();
console.log('ok');
break;
case 1:
/**
* it's not ok, I see only result of "console.log(nul)", line 29
*/
this.foo().then(function () {
console.log(nul);
nul.qqq();
console.log('ok');
});
break;
};
};
return A;
})();
exp.A = A;
}) (exp || (exp = {}));
exp.a = new exp.A();
// You should run functions SEPARATELY!!!
exp.a.bar(0); // in that case: that's all ok, "TypeError: Cannot read property 'qqq' of null"
exp.a.bar(1); // int that case: it's not ok, I see only result of "console.log(nul)", line 29
I don't have any idea how to solve it
The reason why you're not seeing the second error on the console is because Q catches all errors and lets you handle them separately.
You can handle an error in then() by chaining with a catch() function, in your example this can be done this way:
this.foo().then(function () {
console.log(nul);
nul.qqq();
console.log('ok');
}).catch(function(error) {
// do something with error
console.log(error);
});
You can get this behavior also by using a try/catch block inside then() like this:
this.foo().then(function () {
try {
console.log(nul);
nul.qqq();
console.log('ok');
} catch (e) {
console.log(e);
}
});
Old answer
Here are a few options for catching errors in JS/node.js:
Try/Catch blocks
These work like in their Java/C# equivalent, wrap each of the calls you make with a try block and catch an error, handling it in the catch block
try {
exp.a.bar(0);
} catch(e) {
console.log(e);
}
You can also add finally blocks, check the type of an exception/error and so on, you can read more about it on the MDN page
Node.js uncaughtException handler
In node, you can catch all uncaught errors, which will stop your program, by binding a callback to the uncaughtException event like this:
process.on('uncaughtException', function (e) {
console.log('Error: ' + e);
});
This isn't always the best thing to do in a program, but if you really don't want to stop the execution, this is an option.
Finally, I recommend giving a look to this official article for best practices about handling errors in node.js
I have a controller that expose a function that returns some text after a rest call. It works fine, but I'm having trouble testing it with Jasmine. The code inside the promise handler in the test never executes.
The controller:
/* global Q */
'use strict';
angular.module('myModule', ['some.service'])
.controller('MyCtrl', ['$scope', 'SomeSvc', function ($scope, SomeSvc) {
$scope.getTheData = function (id) {
var deferred = Q.defer();
var processedResult = '';
SomeSvc.getData(id)
.then(function (result) {
//process data
processedResult = 'some stuff';
deferred.resolve(processedResult);
})
.fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}]);
The test:
describe('some tests', function() {
var $scope;
var $controller;
var $httpBackend;
beforeEach(function() {
module('myModule');
inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$scope = _$rootScope_.$new();
$controller = _$controller_;
$httpBackend = _$httpBackend_;
//mock the call that the SomeSvc call from the controller will make
$httpBackend.expect('GET', 'the/url/to/my/data');
$httpBackend.whenGET('the/url/to/my/data')
.respond({data:'lots of data'});
$controller ('MyCtrl', {
$scope: $scope
});
});
});
describe('test the returned value from the promise', function() {
var prom = $scope.getTheData(someId);
prom.then(function(result) {
expect(result).toBe('something expected'); //this code never runs
})
});
});
Anything inside a then will not be run unless the promise callbacks are called - which is a risk for a false positive like you experienced here. The test will pass here since the expect was never run.
There are many ways to make sure you don't get a false positive like this. Examples:
A) Return the promise
Jasmine will wait for the promise to be resolved within the timeout.
If it is not resolved in time, the test will fail.
If the promise is rejected, the test will also fail.
Beware If you forget the return, your test will give a false positive!
describe('test the returned value from the promise', function() {
return $scope.getTheData(someId)
.then(function(result) {
expect(result).toBe('something expected');
});
});
B) Use the done callback provided by Jasmine to the test method
If done is not called within the timeout the test will fail.
If done is called with arguments the test will fail.
The catch here will pass the error to jasmine and you will see the error
in the output.
Beware If you forget the catch, your error will be swallowed and your test will fail with a generic timeout error.
describe('test the returned value from the promise', function(done) {
$scope.getTheData(someId)
.then(function(result) {
expect(result).toBe('something expected');
done();
})
.catch(done);
});
C) Using spies and hand cranking (synchronous testing)
If you're not perfect this might be the safest way to write tests.
it('test the returned value from the promise', function() {
var
data = { data: 'lots of data' },
successSpy = jasmine.createSpy('success'),
failureSpy = jasmine.createSpy('failure');
$scope.getTheData(someId).then(successSpy, failureSpy);
$httpBackend.expect('GET', 'the/url/to/my/data').respond(200, data);
$httpBackend.flush();
expect(successSpy).toHaveBeenCalledWith(data);
expect(failureSpy).not.toHaveBeenCalled();
});
Synchronous testing tricks
You can hand crank httpBackend, timeouts and changes to scope when needed to get the controller/services to go one step further. $httpBackend.flush(), $timeout.flush(), scope.$apply().
In case there is a promise created by $q somewhere (and since you seem to be using $httpBackend, then this might well be the case), then my suggestions are to trigger a digest cycle after the call to getTheData, and make sure that the call to expect isn't in a then callback (so that if something is broken, the test fails, rather than just not run).
var prom = $scope.getTheData(someId);
$scope.$apply();
expect(result).toBe('something expected');
The reason this might help is that Angular promises are tied to the digest cycle, and their callbacks are only called if a digest cycle runs.
The problem you face is that you are testing async code (promise based) using synchronous code. That won't work, as the test has finished before the Thenable in your test code has started running the callback containing the test. The Jasmine docs specifically show how to test async code (same as Mocha), and the fix is very small:
describe('test the returned value from the promise', function(done) {
var prom = $scope.getTheData(someId);
prom.then(function(result) {
expect(result).toBe('something expected'); //this code never runs
done(); // the signifies the test was successful
}).catch(done); // catches any errors
});
I would like to test the error handling of a method, which schedules work with setTimeout. The error will be thrown in the scheduled part, i.e.:
function sutWithSetTimeout() {
setTimeout(function () { throw new Error("pang"); }, 1);
}
How do I test that an error is thrown and that it has the correct message?
You need to catch the function in the setTimeout and call them using expect(function(){fn();}).toThrow(e);. So you can spy on setTimeout to get the function:
spyOn(window, 'setTimeout');
sutWithSetTimeout();
var fn = window.setTimeout.mostRecentCall.args[0];
expect(fn).toThrow('pang');