I'm stuck with testing promies in Chai and Sinon. Generally I got service with is wrapper for xhr request and it returns promises. I tried to test it like that:
beforeEach(function() {
server = sinon.fakeServer.create();
});
afterEach(function() {
server.restore();
});
describe('task name', function() {
it('should respond with promise error callback', function(done) {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
service.get('/someBadUrl').then(spy1, spy2);
server.respond();
done();
expect(spy2.calledOnce).to.be.true;
expect(sp2.args[0][1].response.to.equal({status: 404, text: 'Not Found'});
});
});
My notes about this:
// spy2 is called after expect finish assertion
// tried with var timer = sinon.useFakeTimers() and timer.tick(510); with no results
// tried with chai-as-promised - don’t know how use it :-(
// cannot install sinon-as-promised only selected npm modules available in my environment
Any any ideas how fix this code/ test this service module?
There's various challenges here:
if service.get() is asynchronous, you need to wait for its completion before checking your assertions;
since the (proposed) solution checks the assertions in a promise handler, you have to be careful with exceptions. Instead of using done(), I would opt for using Mocha's (which I assume you're using) built-in promise support.
Try this:
it('should respond with promise error callback', function() {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
// Insert the spies as resolve/reject handlers for the `.get()` call,
// and add another .then() to wait for full completion.
var result = service.get('/someBadUrl').then(spy1, spy2).then(function() {
expect(spy2.calledOnce).to.be.true;
expect(spy2.args[0][1].response.to.equal({status: 404, text: 'Not Found'}));
});
// Make the server respond.
server.respond();
// Return the result promise.
return result;
});
Related
I am testing an angular service using karma/jasmine and one of my service functions is as follows. I need to get coverage to 100%, but can't seem to figure out how to test both success and error cases..
function getAccount(accountId) {
var defer = $q.defer(), myService;
myService = Restangular.all('Some/Url/Path');
myService.get('', {}, {
'header-info': 'bla'
})
.then(function onSuccess(response) {
defer.resolve(response);
}, function onError() {
someMethodCall();
});
return defer.promise;
}
In my corresponding .spec test file, I have:
it('should succeed in getting account', function() {
httpBackend.whenGET('Some/Url/Path').respond(200, mockResponse);
var promise = myServices.getAccount('account123');
promise.then(function(response) {
expect(response).toEqual(mockResponse);
});
it('should error out in getting account', function() {
httpBackend.whenGET('Some/Url/Path').respond(500, '');
var promise = myServices.getAccount('account123');
promise.then(function() {
expect(someMethodCall).toHaveBeenCalled();
});
Right now, both cases "pass", but I'm not getting the branch coverage for the onError case. Something seems fishy about the onSuccess case passing too.
Basically I am asking what is the correct syntax and way of writing the test cases such that I can hit both success and on error cases when I make a 200 and a 500 call to my API
Since you don't have any calls to $http in your service, I would recommend mocking Restangular instead of using httpBackend. This way your test doesn't have to know anything about the implementation details of Restangular, other than what it returns, just like your service.
Mock example:
var Restangular = {
all: function() {
return {
get: function() {
restangularDeferred = $q.defer();
return restangularDeferred.promise;
}
};
}
};
Now you can easily either resolve or reject restangularDeferred depending on what you want to test.
Set up your module to use the mock:
module('myApp', function($provide) {
$provide.value('Restangular', Restangular);
});
Example test of success case:
it('success', function() {
// If you want you can still spy on the mock
spyOn(Restangular, 'all').and.callThrough();
var mockResponse = {};
var promise = myServices.getAccount('account123');
promise.then(function(response) {
expect(response).toEqual(mockResponse);
expect(Restangular.all).toHaveBeenCalledWith('Some/Url/Path');
});
restangularDeferred.resolve(mockResponse);
// Trigger the digest loop and resolution of promise callbacks
$rootScope.$digest();
});
Example test of error case:
it('error', function() {
spyOn(anotherService, 'someMethodCall');
var mockResponse = {};
myServices.getAccount('acount123');
restangularDeferred.reject(mockResponse);
$rootScope.$digest();
expect(anotherService.someMethodCall).toHaveBeenCalled();
});
Note that I moved someMethodCall into anotherService in the example.
Demo: http://plnkr.co/edit/4JprZPvbN0bYSXFobgmu?p=preview
I have a node.js app using express 4 and this is my controller:
var service = require('./category.service');
module.exports = {
findAll: (request, response) => {
service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
It calls my service which returns a promise. Everything works but I am having trouble when trying to unit test it.
Basically, I would like to make sure that based on what my service returns, I flush the response with the right status code and body.
So with mocha and sinon it looks something like:
it('Should call service to find all the categories', (done) => {
// Arrange
var expectedCategories = ['foo', 'bar'];
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.resolves(expectedCategories);
var response = {
status: () => { return response; },
send: () => {}
};
sandbox.spy(response, 'status');
sandbox.spy(response, 'send');
// Act
controller.findAll({}, response);
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
expect(response.send).to.be.called; // not working
done();
});
I have tested my similar scenarios when the function I am testing returns itself a promise since I can hook my assertions in the then.
I also have tried to wrap controller.findAll with a Promise and resolve it from the response.send but it didn't work neither.
You should move your assert section into the res.send method to make sure all async tasks are done before the assertions:
var response = {
status: () => { return response; },
send: () => {
try {
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
// expect(response.send).to.be.called; // not needed anymore
done();
} catch (err) {
done(err);
}
},
};
The idea here is to have the promise which service.findAll() returns accessible inside the test's code without calling the service. As far as I can see sinon-as-promised which you probably use does not allow to do so. So I just used a native Promise (hope your node version is not too old for it).
const aPromise = Promise.resolve(expectedCategories);
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.returns(aPromise);
// response = { .... }
controller.findAll({}, response);
aPromise.then(() => {
expect(response.status).to.be.calledWith(200);
expect(response.send).to.be.called;
});
When code is difficult to test it can indicate that there could be different design possibilities to explore, which promote easy testing. What jumps out is that service is enclosed in your module, and the dependency is not exposed at all. I feel like the goal shouldn't be to find a way to test your code AS IS but to find an optimal design.
IMO The goal is to find a way to expose service so that your test can provide a stubbed implementation, so that the logic of findAll can be tested in isolation, synchronously.
One way to do this is to use a library like mockery or rewire. Both are fairly easy to use, (in my experience mockery starts to degrade and become very difficult to maintain as your test suite and number of modules grow) They would allow you to patch the var service = require('./category.service'); by providing your own service object with its own findAll defined.
Another way is to rearchitect your code to expose the service to the caller, in some way. This would allow your caller (the unit test) to provide its own service stub.
One easy way to do this would be to export a function contstructor instead of an object.
module.exports = (userService) => {
// default to the required service
this.service = userService || service;
this.findAll = (request, response) => {
this.service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
var ServiceConstructor = require('yourmodule');
var service = new ServiceConstructor();
Now the test can create a stub for service and provide it to the ServiceConstructor to exercise the findAll method. Removing the need for an asynchronous test altogether.
I am trying to create unit tests with Mocha and Chai on Node.JS. Here is a simplified version of the function to test:
router.cheerioParse = function(url, debugMode, db, theme, outCollection, _callback2) {
var nberror = 0;
var localCount = 0;
console.log("\nstarting parsing now : " + theme);
request(url, function(error, response, body) {
//a lot of postprocessing here that returns
//true when everything goes well)
});
}
Here is the test I am trying to write:
describe('test', function(){
it('should find documents', function(){
assert( true ==webscraping.cheerioParse("http://mytest.com, null, null, null ,null,null ));
});
})
How can the request function return true to have it passed to the test? I have tried to use promises but it didn't work either. In this case should I put the return statement in the then callback? What is the best approach?
You should mock request function. You could use e.g. sinon stubs for this (they provide returns function for defining returning value).
In general - the idea of unit tests is to separate particular function (unit of test) and stub every other dependency, as you should do with request :)
To do so, you have to overwrite original request object, e.g. :
before(function() {
var stub = sinon.stub(someObjectThatHasRequestMethod, 'request').returns(true);
});
And after running tests you should unstub this object for future tests like that:
after(function() {
stub.restore();
});
And that's all :) You could use both afterEach/after or beforeEach/before - choose the one that suits you better.
One more note - because your code is asynchronous, it is possible that your solution might need more sophisticated way of testing. You could provide whole request mock function and call done() callback when returning value like this:
it('should find documents', function(done) {
var requestStub = sinon.stub(someObjectThatHasRequestMethod, 'request',
function(url, function (error, response, body) {
done();
return true;
}
assert(true === webscraping.cheerioParse("http://mytest.com, null, null, null ,null,null ));
requestStub.restore();
});
You could find more info here:
Mocha - asynchronous code testing
I am attempting to test some asynchronous JavaScript (that was once TypeScript) using Jasmine. I've had a hard time getting this to work correctly, and with this simple example it never makes it to the then(function( code block and I get the following error:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
My test looks something like:
it("Should work", function(done){
dataService.ready = true;
dataService.isReady().then(function(result){
console.log(result);
expect(result).toBe(true);
done();
});
});
And the service I'm testing looks something like (before compiled to JavaScript):
public isReady(): angular.IPromise<any> {
var deferred = this.q.defer();
if (this.ready) {
setTimeout(() => { return deferred.resolve(true); }, 1);
} else {
// a bunch of other stuff that eventually returns a promise
}
return deferred.promise;
}
I am sure I am just misusing done() but I feel like this should work! Any suggestions?
UPDATE:
For further debugging, I added a few console logs within the isReady() function. It now looks like:
public isReady(): angular.IPromise<any> {
var deferred = this.q.defer();
console.log("in isReady()"); // new line to add logging
if (this.ready) {
console.log("this.ready is true"); // new line to add logging
setTimeout(() => {
console.log("returning deferred.resolve"); // new line to add logging
return deferred.resolve(true);
}, 1);
} else {
// a bunch of other stuff that eventually returns a promise
}
return deferred.promise;
}
isReady() works as expected when I manually test in a browser. When running the test, my logs include:
LOG: 'in isReady()'
LOG: 'this.ready is true'
LOG: 'returning deferred.resolve'
Within my test, it appears to never be resolved (code block within then() is never executed) but when running my app this function works just fine. This example is in a controller:
DataService.isReady().then(() => {
console.log("I work!");
});
UPDATE: And more debugging...
In my test:
it("Should work", function(done){
console.log("calling dataService.isReady()");
var prom = dataService.isReady();
console.log("promise before");
console.log(prom);
setTimeout(function(){
console.log("promise after");
console.log(prom);
},1000);
prom.then(function(result){
// never makes it here
done();
}, function(reason) {
// never makes it here either
});
}
Now, in my console, I see:
LOG: 'calling dataService.isReady()'
LOG: 'in isReady()'
LOG: 'this.ready is true'
LOG: 'promise before'
LOG: Object{$$state: Object{status: 0}}
LOG: 'returning deferred.resolve'
LOG: 'promise after'
LOG: Object{$$state: Object{status: 1, pending: [...], value: true, processScheduled: true}}
So, my promise looks like it should. Why isn't then() being invoked?
What was actually happening:
So, it turns out my question should have been something like "Why doesn't my angular promise resolve in my Jasmine test?"
$digest
After a bit of digging and looking at other solutions, I found some good information about when/how promises are resolved. I needed to call $digest() on the $rootScope in order to resolve the promise and execute the then() code block (and therefore call done() to satisfy the spec).
No more request expected error
Adding $rootScope.$digest() got me most of the way there, but then I started discovering a No more request expected error that was causing my tests to fail. This was because the service I am using is sending off various POST and GET requests for another aspect of my application. Stubbing out a whenGET and whenPOST response seemed to solve that issue.
The Final Solution:
Long story short, my spec file now looks like:
describe("Async Tests", function(){
var dataService;
var rootScope;
var httpBackend;
beforeEach(module("myangularapp"));
beforeEach(inject(function(_$httpBackend_, _DataService_, $rootScope){
dataService = _DataService_;
rootScope = $rootScope;
httpBackend = _$httpBackend_;
// solves the 'No more request expected' errors:
httpBackend.whenGET('').respond([]);
httpBackend.whenPOST('').respond([]);
}));
it("Should work", function(done){
dataService.ready = true;
dataService.isReady().then(function(result){
console.log(result);
expect(result).toBe(true);
// still calls done() just as before
done();
});
// digest the scope every so often so we can resolve the promise from the DataService isReady() function
setInterval(rootScope.$digest, 100);
});
});
This solution seems more complex than it needs to be, but I think it'll do the trick for now. I hope this helps anyone else who may run into challenges with testing async code with Angular and Jasmine.
Another way to handle this is to always use .finally(done) when testing promises and then call $timeout.flush() afterwards.
"use strict";
describe('Services', function() {
var $q;
var $timeout;
// Include your module
beforeEach(module('plunker'));
// When Angular is under test it provides altered names
// for services so that they don't interfere with
// outer scope variables like we initialized above.
// This is nice because it allows for you to use $q
// in your test instead of having to use _q , $q_ or
// some other slightly mangled form of the original
// service names
beforeEach(inject(function(_$q_, _$timeout_) {
$q = _$q_;
// *** Use angular's timeout, not window.setTimeout() ***
$timeout = _$timeout_;
}));
it('should run DataService.isReady', function(done) {
// Create a Dataservice
function DataService() {}
// Set up the prototype function isReady
DataService.prototype.isReady = function () {
// Keep a reference to this for child scopes
var _this = this;
// Create a deferred
var deferred = $q.defer();
// If we're ready, start a timeout that will eventually resolve
if (this.ready) {
$timeout(function () {
// *** Note, we're not returning anything so we
// removed 'return' here. Just calling is needed. ***
deferred.resolve(true);
}, 1);
} else {
// a bunch of other stuff that eventually returns a promise
deferred.reject(false);
}
// Return the promise now, it will be resolved or rejected in the future
return deferred.promise;
};
// Create an instance
var ds = new DataService();
ds.ready = true;
console.log('got here');
// Call isReady on this instance
var prom = ds.isReady();
console.log(prom.then);
prom.then(function(result) {
console.log("I work!");
expect(result).toBe(true);
},function(err) {
console.error(err);
}, function() {
console.log('progress?');
})
// *** IMPORTANT: done must be called after promise is resolved
.finally(done);
$timeout.flush(); // Force digest cycle to resolve promises;
});
});
http://plnkr.co/edit/LFp214GQcm97Kyv8xnLp
I'm trying to write some tests with Jasmine, but now have a problem if there are some code is asynchronous in beforeEach.
The sample code looks like:
describe("Jasmine", function() {
var data ;
beforeEach(function(){
console.log('Before each');
getSomeDataFromRemote(function(res){
data = res;
});
});
it("test1", function() {
expect(data).toBe(something);
console.log('Test finished');
});
});
You can see, in the beforeEach, I want to get some data from remote, and assign it to the data asynchronously.
But in the test1, when I try to verify:
expect(data).toBe(something);
The data is undefined, because getSomeDataFromRemote has not finished yet.
How to fix it?
Just like the async stuff within an it you can use the runs and waitsFor in your beforeEach:
define( 'Jasmine' , function () {
var data ;
beforeEach(function(){
runs( function () {
getSomeDataFromRemote(function(res){
data = res;
});
});
waitsFor(function () { return !!data; } , 'Timed out', 1000);
});
it("test1", function() {
runs( function () {
expect(data).toBe(something);
});
});
});
Although I'm going to assume that it's because this was test code I think you should probably have the getSomeDataFromRemote call inside your it as that's actually what you're testing ;)
You can see some larger examples in some tests I've written for an async API here: https://github.com/aaronpowell/db.js/blob/f8a1c331a20e14e286e3f21ff8cea8c2e3e57be6/tests/public/specs/open-db.js
Jasmine 2.0
Be careful because in the new Jasmine 2.0 this is going to change and it will be mocha style. You have to use done() function in beforeEach() and it(). For example, imagine you want to test if a page exists and is not empty, in a LAMP server, using jQuery $.get. First you need to add jQuery to the SpecRunner.html file, and in your spec.js file:
describe('The "index.php" should', function() {
var pageStatus;
var contents;
beforeEach(function (done) {
$.get('views/index.php', function (data, status) {
contents = data;
pageStatus = status;
done();
}).fail(function (object, status) {
pageStatus = status;
done();
});
});
it('exist', function(done) {
expect(status).toBe('success');
done();
});
it('have content', function(done) {
expect(contents).not.toBe('');
expect(contents).not.toBe(undefined);
done();
});
});
As you can see, you pass the function done() as a parameter for beforeEach() and it(). When you run the test, it() won't be launched until done() has been called in beforeEach() function, so you are not going to launch the expectations until you have the response from the server.
The page exists
If the page exists we capture the status and the data from the response of the server, and we call done(). Then we check if the status is "success" and if the data is not empty or undefined.
The page does not exist
If the page does not exist we capture the status from the response of the server, and we call done(). Then we check if the status is not "success" and if the data is empty or undefined (that must be because the file does not exist).
In this case I typically stub the asynchronous call to respond immediately.
I'm not sure if you've seen it or not, but here is some documentation about asynchronous testing with Jasmine.