Deferring promises in Karma test of angular ngResources - javascript

Currently I'm using ngResource for my RESTful API calls, and I'm using KARMA & jasmine to do my unit and integration tests.
Inside one Controller i have a function that expects promise to be finished:
var elem = new Element() // calling a ngResource Factory
elem.$save().then(function () {
$scope.elem.push(elem);
});
In my karma tests, i test if the list is empty, that call the function above and check if the $scope.elem Array does have the new created element. But since its a promise, KARMA test does not solve this. I tried to use $rootScope.apply(), but the $httpBackend expects that i define lots of calls, that are expected. But i just want to simulate the call.
Is there any elegant solution for that?

There is not elegant solution for this. Main purpose of testing is prepare "isolated" ecosystem for test. If you want to test ajax call , they must return something. You cant test and just tell "skip this promise and act as like it was success".
When you need to resolve any promise, i am using this.
$httpBackend.flush();
$rootScope.$apply();
This will call $httpBackend, and ofcourse it will expecting call. You have no choice
1.) Mock all backend calls (this is taken from my test)
identityBackend = $httpBackend.when("GET", AppConfig.API_IDENTITY_ENDPOINT + "/me",null,function(headers)
{
return headers.Authorization !== undefined;
}).respond(200, fakeAuthUser);
So it will respond with http 200 with fake json on request host/me , when authorization token inside headers is not undefined.
2.) Second choice, create mockable backend inside nodejs/express and mock all requests with jsons. Before starting jasmine test , you will also start this "fake" backend server.

Related

Angular Unit tests: Mocking multiple independent promises

This is a long one, so I will begin by asking the question I struggle with:
How do I resolve independent promises for the same function that has been run with different parameters in unit testing, and get different values?
I have difficulties with mocking an environment where multiple http-requests are executed, independent of each other, but with the same service-object.
It works in real application, but setting up a proper mocking environment for unit-testing (Jasmine, Karma) has proven quite difficult.
Let me explain the environment, and what I have tried to to:
First off, I have an Angular Controller that makes a single http-request with a custom service object, and mocking this in the tests works. Then I have made a Controller that makes multiple independent http-requests with the same service object, and I have attempted at expanding my unit testing to cover this one, given my success with the other controller.
Background on how it works in controller with single request/promise:
If you don't want to go through all this, you can jump straight to The real problem: Testing multiple independent requests and promises. You probably should.
Let us first go with the single-request controller and its working test, to have a foundation.
SingleRequestController
function OpenDataController($scope, myHttpService) {
$scope.parameterData = {requestString : "A"};
$scope.executeSingleRequest = function() {
myHttpService.getServiceData($scope.parameterData)
.then(function (response) {
$scope.result = response.data;
});
}
// Assume other methods, that calls on $scope.executeSingleRequest, $scope.parameterData may also change
}
As you probably figure, myHttpService is a custom service that sends a http-request to a set URL, and adds in the parameters passed on by the controller.
SingleRequestControllerTest
describe('SingleRequestController', function() {
var scope, controller, myHttpServiceMock, q, spy;
beforeEach(module('OppgaveregisteretWebApp'));
beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {
rootScope = $rootScope;
scope = rootScope.$new();
q = $q;
spy = spyOn(myHttpService, 'getServiceData');
// Following are uncommented if request is executed at intialization
//myHttpServiceMock= q.defer();
//spy.and.returnValue(myHttpServiceMock.promise);
controller = $controller('OpenDataController', {
$scope: scope,
httpService: httpService
});
// Following are uncommented if request is executed at intialization
//myHttpServiceMock.resolve({data : "This is a fake response"});
//scope.$digest();
}));
describe('executeSingleRequest()', function () {
it('should update scope.result after running the service and receive response', function () {
// Setup example
scope.parameterdata = {requestString : "A", requestInteger : 64};
// Prepare mocked promises.
myHttpServiceMock= q.defer();
spy.and.returnValue(myHttpServiceMock.promise);
// Execute method
scope.executeSingleRequest();
// Resolve mocked promises
myHttpServiceMock.resolve({data : "This is a fake response"});
scope.$digest();
// Check values
expect(scope.result).toBe("This is a fake response");
});
});
});
This is a light-weight pseudo copy of a real life implementation I'm working with. Suffice to say, I have, through trying and failing, discovered that for each and every call on myHttpService.getServiceData (usually by directly calling $scope.executeSingleRequest, or indirectly through other methods), the following has to be done:
myHttpServiceMock must be initialized anew (myHttpServiceMock= q.defer();),
initialize spy to return mocked promise (spy.and.returnValue(myHttpServiceMock.promise);)
Execute the call to the service
Resolve the promise (myHttpServiceMock.resolve({data : "This is a fake response"});)
Call digest (q.defer();)
So far, it works.
I know it's not the most beautiful code, and for each time the mocked promise has to be initialized and then resolved, a method encapsulating these would be preferable in each test. I've chosen to show it all here for demonstrative purpose.
The real problem: Testing multiple independent requests and promises:
Now, let us say the controller does multiple independent requests to the service, with different parameters. This is the case in a similar controller in my real life application:
MultipleRequestsController
function OpenDataController($scope, myHttpService) {
$scope.resultA = "";
$scope.resultB = "";
$scope.resultC = "";
$scope.resultD = "";
$scope.executeRequest = function(parameterData) {
myHttpService.getServiceData(parameterData)
.then(function (response) {
assignToResultBasedOnType(response, parameterData.requestType);
});
}
$scope.executeMultipleRequestsWithStaticParameters = function(){
$scope.executeRequest({requestType: "A"});
$scope.executeRequest({requestType: "B"});
$scope.executeRequest({requestType: "C"});
$scope.executeRequest({requestType: "D"});
};
function assignToResultBasedOnType(response, type){
// Assign to response.data to
// $scope.resultA, $scope.resultB,
// $scope.resultC, or $scope.resultD,
// based upon value from type
// response.data and type should differ,
// based upon parameter "requestType" in each request
...........
};
// Assume other methods that may call upon $scope.executeMultipleRequestsWithStaticParameters or $scope.executeRequest
}
Now, I realize that "assignToResultBasedOnType" may not be the best way to handle the assignment to the correct property, but that is what we have today.
Usually, the four different result-properties receive the same type of object, but with different content, in the real life application.
Now, I want to simulate this behavior in my test.
MultipleRequestControllerTest
describe('MultipleRequestsController', function() {
var scope, controller, myHttpServiceMock, q, spy;
var lastRequestTypeParameter = [];
beforeEach(module('OppgaveregisteretWebApp'));
beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {
rootScope = $rootScope;
scope = rootScope.$new();
q = $q;
spy = spyOn(myHttpService, 'getServiceData');
controller = $controller('OpenDataController', {
$scope: scope,
httpService: httpService
});
}));
describe('executeMultipleRequestsWithStaticParameters ()', function () {
it('should update scope.result after running the service and receive response', function () {
// Prepare mocked promises.
myHttpServiceMock= q.defer();
spy.and.callFake(function (myParam) {
lastRequestTypeParameter.unshift(myParam.type);
return skjemaHttpServiceJsonMock.promise;
// Execute method
scope.executeMultipleRequestsWithStaticParameters();
// Resolve mocked promises
myHttpServiceMock.resolve(createFakeResponseBasedOnParameter(lastRequestTypeParameter.pop()));
scope.$digest();
// Check values
expect(scope.resultA).toBe("U");
expect(scope.resultB).toBe("X");
expect(scope.resultC).toBe("Y");
expect(scope.resultD).toBe("Z");
});
});
function createFakeResponseBasedOnParameter(requestType){
if (requestType==="A"){return {value:"U"}}
if (requestType==="B"){return {value:"X"}}
if (requestType==="C"){return {value:"Y"}}
if (requestType==="D"){return {value:"Z"}}
};
});
This is what happens in the test (discovered during debug):
The spy function runs four times, and pushes in the values to the array lastRequestTypeParameter, which will be [D, C, B, A], which values are supposed will be popped to read A-B-C-D, to reflect the real order of the requests.
However, here comes the problem: Resolve happens only once, and the same response is created for all four result-properties: {value:"U"}.
The correct list is selected internally, because the promise-chain uses the same parameter values as was used in the service-call (requestType), but they all receive data only on the first response. Thus, the result is:
$scope.resultA = "U"; $scope.resultB = "U", and so on.... instead of U, X, Y, Z.
So, the spy function runs four times, and I had assumed that four promises were returned, one for each call. But as of now, there is only one resolve() and one q.digest().
I have tried the following, to make things work:
Four q.defer()
Four resolves
Four digests
Return an array with four different objects, corresponding to what I would expect in working test. (Silly, I know, it differs from the expected object structure, but what don't you do when you try to tweak anything to get a surprisingly working result?).
None of these work. In fact, the first resolve causes the same result to all four properties, so adding more resolves and digests will make little difference.
I have tried to Google this issue, but all I find are either multiple promises for different services, multiple chain-functions (.then().then()...), or nested asynchronous calls (new promise object(s) inside chain).
What I need is a solution for independent promises, created by running the same function with different parameters.
So, I will end with the question I opened up with:
How do I resolve independent promises for the same function that has been run with different parameters in unit testing, and get different values?
Jasmine is Angular-friendly Jack of all trades. It is generally suitable for the majority of front-end testing cases. It lacks in spying/mocking functionality, while Sinon offers much more power.
This may be the reason why Mocha/Sinon/Chai modular bundle may be preferred at some point, but the good thing about its modularity is that Sinon isn't tied to the bundle. Besides its tight relations with Chai, it can also be used with Jasmine matchers.
The thing that makes Sinon a better choice than Jasmine spies is that it is capable of programming spies expectations (withArgs(...).called...) and stubs responses (withArgs(...).returns(...)). Blue-collar mocking becomes a piece of cake:
var sandbox;
var spy;
// beforeEach
sandbox = sinon.sandbox.create();
// similar to Jasmine spy without callThrough
spy = sandbox.stub(myHttpService, 'getServiceData');
...
// it
spy.withArgs('A').returns({value:"U"});
spy.withArgs('B').returns({value:"X"});
...
// afterEach
sandbox.restore(); // the thing that Jasmine does automatically for its spies
Regarding once-resolved promise, this is the expected behaviour. As a rule of thumb fresh promises should be returned from mocked functions, never an existing object with .returnValue in Jasmine (or .returns in Sinon).
A callback function should be used to return a fresh promise on each call. If the promise should be resolved with predefined value, there may be several patterns to achieve this, the most obvious is using a variable
var mockedPromiseValue;
...
spy = spyOn(myHttpService, 'getServiceData')
.and.callFake(() => $q.resolve(mockedPromiseValue));
...
mockedPromiseValue = ...;
myHttpService.getServiceData().then((result) => {
expect(result).toBe(...);
})
// rinse and repeat
$rootScope.$digest();

Global BeforeEach for mocking HTTP request with Mocha and Angular

I have few requests that are triggered in module.run:
angular.module('demo').run(function($http) {
$http.get('/some/thing');
$http.get('/some/other/thing');
});
And when I use $rootScope.$apply in my tests to resolve mock promises, I get unexpected request errors for '/some/thing' and '/some/other/thing'.
One way to fix it is to set $httpBackend in the beforeeach:
$httpBackend.when('GET', mockData.API_URL + '/some/thing').respond(200, {});
$httpBackend.when('GET', mockData.API_URL + '/some/other/thing').respond(200, {});
This will work but it means that I have to put it into beforeeach of every test file where I use $rootScope.$apply.
How can I make those $httpBackend configs global for every test file?
Or is there a better solution to this problem?
From the Mocha website, at "Root-Level Hooks":
You may also pick any file and add "root"-level hooks. For example,
add beforeEach() outside of all describe() blocks. This will cause the
callback to beforeEach() to run before any test case, regardless of
the file it lives in (this is because Mocha has a hidden describe()
block, called the "root suite").
beforeEach(function() {
console.log('before every test in every file');
});
If you really need it in all tests VinceOPS answer is best. What I would do if you don't need it in every test but just a lot is move the $httpBackend calls into a separate function and put that in a shared js file. Then you just call that function from beforeEach when needed.
For more complex configurations I often create my own wrapper functions for either describe(), it() or the function where you define the test to prevent writing (too much) duplicate code.

How to properly test an AngularJS Controller Function

We just started implementing jasmine tests in our AngularJS project and I have a question:
We want to test this controller function:
$scope.deleteClick = function () {
$scope.processing = true;
peopleNotesSrv.deleteNote($scope.currentOperator.operatorId, $scope.noteId, $scope.deleteSuccessCallback, $scope.deleteErrorCallback);
};
We wrote this following test:
it('deleteClick should pass proper parameters to peopleNoteSrv', function () {
$controllerConstructor('PeopleNoteEditCtrl', { $scope: $scope });
$scope.noteId = 5;
expect(function () { $scope.deleteClick(); }).not.toThrow();
});
This test makes sure that when we call the $scope.deleteClick() function that $scope.processing is set to true and that the call to peopleNotesSrv doesn't throw any errors because of invalid arguments. We are testing the two callback functions in separate tests.
Should we be testing that the peopleNotesSrv.deleteNote function was called so the test is more explicit? The way this test is written right now it doesn't really tell someone what the deleteClick() function does under the hood and that seems to be incorrect.
Ask yourself what you'd do if you had developed it using TDD. It pretty much goes the direction Sam pointed out, but here are some examples:
Controller Tests
start writing a test which would expect a deleteClick to exist.
Expect deleteClick to setup the loading state (check for processing = true)
Test whether a service is injected into the controller (peopleNotesSrv)
Check whether deleteClick calls the service (as already mentioned via spies)
Verify that $scope.noteId and the other $scope.params are present and set
This is as far as it relates to the Controller. All the criteria whether it fails or throws errors etc. should be tested in a Service.spec. Since I don't know your service in detail here some examples
Service Tests
Ensure deleteNote exists
Check what happens if wrong number of arguments (less or more) are supplied
Make some positive tests (like your noteId = 5)
Make some negative tests
Ensure callbacks are properly called
... and so on.
Testing for validity in controllers doesn't make a lot of sense because than you'd need to do it for every Controller you have out there. By isolating the Service as a separate Unit of Test and ensure that it fulfills all the requirements you can just use it without testing. It's kinda the same as you never would test jQuery features or in case of Angular jQLite, since you simply expect them to do what they should :)
EDIT:
Make controller tests fail on service call
Pretty easy lets take this example. First we create our Service Test to ensure that the call fails if not the proper number of arguments is supplied:
describe('Service: peopleNoteSrv', function () {
// load the service's module
beforeEach(module('angularControllerServicecallApp'));
// instantiate service
var peopleNoteSrv;
beforeEach(inject(function (_peopleNoteSrv_) {
peopleNoteSrv = _peopleNoteSrv_;
}));
it('should throw error on false number of arguments', function () {
expect(function() { peopleNoteSrv.deleteNote('justOneParameter'); }).toThrow();
});
});
Now to ensure that the test passes lets create the error throwing part in our service method
angular.module('angularControllerServicecallApp')
.service('peopleNoteSrv', function peopleNoteSrv() {
this.deleteNote = function(param1, param2, param3) {
if(arguments.length !== 3)
throw Error('Invalid number of arguments supplied');
return "OK";
};
});
Now lets create 2 demo controllers, FirstCtrl will do it properly, but SecondCtrl should fail
angular.module('angularControllerServicecallApp')
.controller('FirstCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('param1', 'param2', 'param3');
}
});
angular.module('angularControllerServicecallApp')
.controller('SecondCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('onlyOneParameter');
}
});
And both controller as a demo have following test:
it('should call Service properly', function () {
expect(scope.doIt()).toBe("OK");
});
Karma now spits out something like this:
Error: Invalid number of arguments supplied
at [PATH]/app/scripts/services/peoplenotesrv.js:15
at [PATH]/app/scripts/controllers/second.js:13
at [PATH]/test/spec/controllers/second.js:20
Thus you exactly know that you missed to update SecondCtrl. Of course this should work for any of your tests consuming the Service method.
Hope that's what you meant.
I think the answer is that it depends.
There are two cases:
1 - You also have a suite of tests for the peopleNotesSrv service.
In this case I would leave this test as-is or check a few more things around the specific functionality of $scope.deleteClick(), such as if there are any watchers on $scope.processing that do anything specific regarding a .deleteClick() call.
2 - You do not have any tests for all the possible functionality for the peopleNotesSrv service.
In this case I would write a more explicit test that does check that the .deleteNote() actually performed it's job.
In my opinion you should really build tests up and try to not test the same thing in more than one place, as this adds extra work and could produce holes in the tests if you think, "Well I can just test this specific case when it gets called from a specific function that calls it."
What if you ever want to reuse that deletNote() as part of a bigger function in a different place?Then you need to write another test for the same code because it is being called from a different function.
So I would aim for case 1, this way you can write all your tests for that service and then trust that those tests cover the rest of this particular test. If you throw errors on bad input or for failures to actually delete a note, you should trust that other code to test what it was designed to test. This will greatly speed up your test-writing time and increase the chance that your tests cover all the cases. It also keeps all the tests for that service in the same place in your test code.
I think also a good question to start with is what kind of test is this? Unit Test or End-to-End test?
I was assuming it was a Unit Test for my answer, if it was an End-to-End test, then you might want to keep following the function calls to verify everything is happening as you expect.
Here are some links on Unit Tests, End-to-End tests, and a pretty good article about both and Angular.
What's the difference between unit, functional, acceptance, and integration tests? (End-to-End tests can also be called Integration test)
http://www.sitepoint.com/unit-and-e2e-testing-in-angularjs/

Mocking Angular $resource

Could anybody suggest me a way how to mock $resource object
I've searched though internet, but all my tries were finished by KARMA testing.
I don't need it.
My idea is to have just fake object, so I will be able to switch between $resource implementations in my app.
Thanks.
You can use $provide to do this.
angular.module(“MyApp”,[])
.config([“$provide”,function($provide){
$provide.decorator(“$resource”,function($delegate, myReplacementResource){
//$delegate is the original $resource, if you just want to modify it
//either inject a replacement resource that you have already registered
//as a factory (recommended). Or make it here.
return myReplacementResource;
});
}])
dskh presented one way to do it. Here's a another way which you might find to be easier... although it's ofen used for unit testing, you can use angular-mocks.js in your app as well:
app.run(function($httpBackend) {
$httpBackend.whenPOST('/string/match/url').respond(function (method, url, data) {
return [{some:data}];
});
$httpBackend.whenGET(/regexpmatch/).respond(function (method, url, data) {
return {some:{other:data}};
});
// pass through other stuff
$httpBackend.whenPOST(/.*/).passThrough();
$httpBackend.whenGET(/.*/).passThrough();
$httpBackend.whenDELETE(/.*/).passThrough();
$httpBackend.whenJSONP(/.*/).passThrough();
$httpBackend.whenPUT(/.*/).passThrough();
});
This plunk shows how I go about mocking resource objects, from a angular service, in a controller. I use SinonJs to fake a resource object. Then I basically fake the promise chain by injecting $q.
To fake the promise chain you need to get a defer object from $q, then get a promise from it.
In your tests, you then either fake a success or a failure by calling promise.resolve() or promise.reject() on that promise. You can fake data from the server by passing an object in as a parameter like this promise.reject(someData).
You then have to make sure that you scope.apply(). To make sure that whatever it is you wanted to do becomes visible on scope.
I'm not entirely sure if this is the right way to go about this, but it has been working for me.

How can i use sinon to stub functions for neo4j Thingdom module

I am having some issues writing some unit tests where i would like to stub out the functionality of the neo4j Thingdom module.
After a few hours of failed attempts i have been searching around the web and the only point of reference i found was a sample project which used to sinon.createStubInstance(neo4j.GraphDatabase); to stub out the entire object. For me, and becuase this seemed to a be a throw away project i wanted a more fine grained approach so i can test that for instance as the Thingdom API outlines when saving a node you create it (non persisted) persist it and then you can index it if you wish which are three calls and could be outlined in multiple specific tests, which i am not sure can be achieved with the createStubInstance setup (i.e. found out if a function was called once).
Example "create node" function (this is just to illustrate the function, i am trying to build it out using the tests)
User.create = function(data, next){
var node = db.createNode(data);
node.save(function(err, node){
next(null,node);
});
};
I am able to stub functions of the top level object (neo4j.GraphDatabase) so this works:
it('should create a node for persistence', function(){
var stub = sinon.stub(neo4j.GraphDatabase.prototype, 'createNode');
User.create({}, res);
stub.calledOnce.should.be.ok;
stub.restore();
});
The issue comes with the next set of test i wish to run which tests if the call to persist the node to the database is called (the node,save) method:
I am not sure if this is possible or it can be achieved but i have tried several variations of the stub and non seem to work (on neo4j.Node, neo4j.Node.prototype) and they all come back with varying errors such as can't wrap undefined etc. and this is probably due to the createNode function generating the node and not my code directly.
Is there something i am glaringly doing wrong, am i missing the trick or can you just not do this? if not what are the best tactics to deal with stuff like this?
A possible solution is to return a stubbed or mocked object, giving you control on what happens after the node is created:
it('should create a node for persistence and call save', function () {
var stubbedNode = {
save: sinon.stub().yields(undefined, stubbedNode)
};
var stub = sinon.stub(neo4j.GraphDatabase.prototype, 'createNode').returns(stubbedNode);
User.create({}, res);
stub.calledOnce.should.be.ok;
stub.restore();
stubbedNode.save.calledOnce.should.be.ok;
});
We couldn't do it directly, the way the module is setup it doesn't work to well with Sinon. What we are doing is simply abstracting the module away and wrapping it in a simple facade/adapter which we are able to stub on our unit tests.
As we are not doing anything bar calling the neo4j module in that class we are integration (and will validate when regression testing) testing that part to make sure we are hitting the neo4j database.

Categories

Resources