I have to test a void method which has a dependent method call that returns a promise and I can't mock that call as it is made on a local object created inside the tested method.
Is there a way to make jasmine expect calls to wait until the promise is resolved? I tried using $rootScope.$digest() but it is not ensuring that the dependent call's promise is resolved.
EDIT: Adding sample code
module.service('serviceToBeTested', ['$rootScope', 'someOtherService',
function($rootScope, someOtherService) {
var thirdPartyLib;
function fnToBeTested() {
//some validations and filtering on rootScope variable to build input for processing
thirdPartyLib = new ThirdPartyLib(); //this is not an angular service
var anotherFunction = function() {
//some hook functions that will be triggered by the third party library
}
// anotherFunction is set into thirdPartyLib so that hook functions will be triggered
thirdPartyLib.start().then(funtion() {
thirdPartyLib.someThing.load(); //this load will trigger one hook function
}
}
}]);
What I need to verify is that, upon invoking fnToBeTested(), a particular logic inside a hook function is executed (for that the control has to go inside the then part of thirdPartyLib.start()).
Actually this gets executed but only after the expect statements in the spec are executed.
And my spec file is almost like this:
it('should do this and this', function() {
// some initialization
serviceToBeTested.fnToBeTested();
$rootScope.$digest();
//expect statements
});
EDIT 2: Adding details on trial made as Andrew suggested below and adding clarity on how instance is created
ThirtPartyLib is instantiated inside main source as:
var theLib = require('theLib');
...............................
thirdPartyLib = new theLib.ThirdPartyLib();
And in spec, I created a var just like this and spied prototype as below:
var theLib = require('theLib');
................................
spyOn(theLib.ThirtPartyLib.prototype, 'start').and.callFake(.....);
But the fake function is not reached. When I check theLib.ThirtPartyLib.prototype in spec during debug, it lists the SpyStrategy while checking theLib.ThirtPartyLib.prototype in main source, it doesn't list that.
You should be able to test this with some clever use of mocking. In your beforeEach block, you can do something like this:
let promise; // declare this outside of your beforeEach so you have access to it in the specs
promise = $q.resolve(); // assign promise to
spyOn(ThirdPartyLib.prototype, 'start').and.returnValue(promise);
And then in your test, you now have access to the promise returned by start.
I'm trying to automate the testing of Angular services which happen to make calls to Parse.com through Parse SDK.
The problem I have got is that the promises dont get resolved unless I explicitely trigger a digest cycle, and the way my services are done, I have to do that in my services implementations which is not sustainable.
My service code is the following :
factory('myService', function($http, $q, $rootScope) {
var myService = {};
myService.simplePromiseTest = function() {
var p = $q.defer();
var query = new Parse.Query("AnyObjectInParse");
query.find().then(function(results){
p.resolve(results);
// *** I have to include that line for the jasmine test to run ***
$rootScope.$apply();
});
return p.promise;
}
}
return myService;
}
And here is my jasmine test
async.it('should resolve the promise', function(done) {
myService.simplePromiseTest().then(function(results) {
// this is never called if don't trigger the digest from the service code
done();
});
// This line is use less as when I get into that line, the promise is not resolved.
// $scope.$root.$digest();
});
So the situation is as following :
I have to wait for the call to parse to end before triggering a digest cycle
I can't find any other solution than to pollute my service's code with this code
I'd like to find a sustainable solution which doesn't require me to update my service's code to pass the test.
Thanks in advance I'm lost with that, I may be missing something obvious :-)
Call $rootScope.$apply(); in the test itself rather than in the promise implementation. Tests with done are asynchronous so it's ok to resolve it afterwards. Alternatively use Angular 1.3.
In general for testing promises I'd probably recommend mocha rather than Jasmine since it supports promise tests out of the box with return statements.
I'm testing a promise with angularjs jasmine, and sinonjs.
I'm puzzled by something regarding promises. Here is my code:
it('should return data with length 4 ', inject(function ($rootScope) {
var storageData;
mockDualStorage.getData.returns($.when(''));
// mockDualStorage.getData is called by getStorageData
// $rootScope.$digest() // not working here
dataGetter.getStorageData().then(function (data) {
console.log(1);
storageData = data;
});
$rootScope.$digest(); // only working here
console.log(2);
expect(storageData.length).toBe(4)// ok
}));
Couple of things are strange here.
If I put $rootScope.$digest() above the dataGetter.getStorageData() then function is never executed.
When the $rootScope.$digest() is below, then gets executed, and order of console.log is 1,2
Why won't then execute when $rootScope.$digest() is above? As I understand promise is already resolved?
After more carefully reading the documentation, found the answer right there.
Differences between Kris Kowal's Q and $q :
There are two main differences: $q is integrated with the $rootScope.Scope Scope model observation mechanism in angular, which means faster propagation of resolution or rejection into your models and avoiding unnecessary browser repaints, which would result in flickering UI.
AngularJS $q service documentation
Say I have an service:
angular.module("app").factory("myService", function($rootScope){
return {
doSomething: function(){
console.log('doingSomething', $rootScope.$$phase);
$rootScope.someVar = true;
}
}
});
If I run it inside the controller like this
angular.module("app").controller("HomeController", function(myService){
myService.doSomething();
});
The console log gives:
doingSomething $apply
But if I run the service inside a unit test environment
it('should doSomething', inject(function(myService) {
myService.doSomething();
}));
The console log only get
doingSomething null
The typical answer that if you need to trigger the $digest cycle yourselves through $apply(). The angular js wiki mentioned about ng-click, $timeout and $http, but surely there are other places, such as running inside the controller. How can I determine that without trial and error?
It really depends on what you are testing. If you are testing something that requires a digest cycle (like $broadcast or $on events), then you will need to call scope.$apply() or simply scope.$digest() in the setup of the test.
However, most of the time, this is not required because services are usually designed as discrete pieces of functionality that do not require the scope. In the case of an HTTP call, the $httpBackend` service allows you to mock out the response.
For ng-click, you shouldn't be using this is a service. And for a controller you'll be binding to a function that you can test in isolation.
Hope this helps.
Although I believe I'm following the instructions here for setting up $httpBackend to pass selected requests to the server, it's not working for me.
Here is a Plunkr with a failing test that shows what I'm doing and explains in comments what seems to be going wrong.
My spelunking suggests that, for some reason, the mock $httpBackend doesn't have an inner copy of the real $httpBackend so that, when it comes time to pass through the XHR request, it passes it to the mock $httpBackend instead. That second call throws an exception because it doesn't know what to do with the request.
Response to dtabuenc
I remember with appreciation your post on midway testing. You identify an important range of integration testing that falls between unit- and E2E-testing. I am standing on that middle ground.
I don't think you are being snarky at all. Your answer is perfect reasonable ... or it would be reasonable if it weren't contradicted by the text of the "API reference / ngMockE2E / $httpBackend". I quote:
This implementation can be used to respond with static or dynamic responses via the when api and its shortcuts (whenGET, whenPOST, etc) and optionally pass through requests to the real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch templates from a webserver) ...
[I]n an end-to-end testing scenario or in a scenario when an application is being developed with the real backend api replaced with a mock, it is often desirable for certain category of requests to bypass the mock and issue a real http request .... To configure the backend with this behavior use the passThrough request handler of when instead of respond.[emphasis mine].
The documentation is silent on the matter of E2E $httpBackend usage within a Jasmine environment. I can't think of a reason to preclude it. If there is such a reason they should state it clearly. Seriously, who reads about a mock component and doesn't anticipate using it in a test environment?
To "pass through requests to the real $httpBackend for specific requests, e.g. to interact with certain remote apis" is precisely what I intend to do. What could they possibly mean by "real $httpBackend" except the non-mock version of that component?
I do not understand your claim that
The ngMocksE2E module is designed to be used on the "server" side of things where the actual angular application is executing.
The word "server" appears exactly 3 times on that page, not once suggesting that any application code would be executed on a "server". I don't know what you mean by the "actual angular application" executing on "the 'server' side of things."
The documentation is perfectly clear that the E2E $httpBackend is not limited to E2E testing. It is also for "a scenario when an application is being developed with the real backend api replaced with a mock".
That's just a half step away from my scenario in which an application is being tested with the real backend api."
In my scenarios, the SUT is calling upon a component which fetches data from a server. My tests exist to verify that this dependent component succeeds in making such requests of the real backend and will retrieve or save data in the expected manner. This is an integration test that cannot be adequately satisfied by mocking the behavior of the backend.
Of course I can test (and do test) with mock XHR responses the component's ability to respond properly to what I predict will be the backend's behavior. That is not the same as validating that the component responds appropriately to the actual backend's behavior ... which might change as the application evolves and depart from the mocked responses in some significant way.
I would consider using your midway tester for this purpose if I understood how to swap it into the SUT's code path. I don't. I think the component making XHR requests is inaccessible to your ngMidwayTester. But I do know how to jam a real XHR helper into the pipeline if I have to.
Here is where I stand at the moment.
Either someone can show how to make $httpBackend pass certain requests through to the server - as the documentation proclaims it can - or I will replace the passThrough implementation myself with a working XHR implementation.
I prefer the first option. If driven to the second, I shall offer a link to it here for the benefit of others who share my needs and my interpretation of the API documentation.
Is there a 3rd way I'm missing?
I stumbled on the same problem but instead of implementing a rich API or replacing the original angular-mocks simply added in the following helper:
angular.module('httpReal', ['ng'])
.config(['$provide', function($provide) {
$provide.decorator('$httpBackend', function() {
return angular.injector(['ng']).get('$httpBackend');
});
}])
.service('httpReal', ['$rootScope', function($rootScope) {
this.submit = function() {
$rootScope.$digest();
};
}]);
It patches two issues that prevent an HTTP request from getting passed through:
Restores original $httpBackend;
Provides a method to fulfill the requests, as otherwise they would be in AngularJS' queue waiting for a digest loop.
describe('my service', function() {
var myService, httpReal;
beforeEach(module('myModule', 'httpReal'));
beforeEach(inject(function( _myService_, _httpReal_ ) {
myService = _myService_;
httpReal = _httpReal_;
}));
it('should return valid data', function(done) {
myService.remoteCall().then(
function(data) {
expect(data).toBeDefined();
done();
}, function(error) {
expect(false).toBeTruthy();
done();
});
httpReal.submit();
});
});
The following is an explanation of the purpose of the $httpBackend that is in the ngMockE2E module.
The ngMockE2E module is simply not designed nor intended to be used from within a jasmine specification.
When doing end-to-end testing there are two sides to the test. One is the angular application that is being tested, the other is the angular-scenario code that lives in the Jasmine Specification.
Under E2E tests there are no angular module, or ng-mocks, or anything angular-related on the jasmine side of things (other than the scenario runner).
The ngMocksE2E module is designed to be used on the "server" side of things where the actual angular application is executing. It's main purpose is to enable us to pre-can responses so that integration-level UI testing can proceed much quicker than if each page actually went to the server for JSON.
When using jasmine along with ng-mocks, angular will always replace the $httpBackend with the mock backend. When adding the ngMocksE2E module it will not be able to get ahold of any "real" $httpBackend and as you have already found out, will just wrap the mock and delegate to it on the pass-through.
It would seem that the kind of test you are trying to write is a test that doesn't test the UI integration, but tests the application javascript and server integration.
This is perfectly legitimate style of testing (referred to some as 'midwayTesting' in the angular community). Your problem is you are using the wrong tool.
I would take a look at this:
https://github.com/yearofmoo/ngMidwayTester
Which you would use instead of angular-mocks and angular.module() in order to facilitate the kind of testing I'm assuming you want to do.
You can read more about it here:
http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html
(apologies if you have already been linked there)
EDIT: (To address additional comments in question)
You have a real beef in that the documentation is not clear that ngMockE2E cannot be used on the client (i.e. karma/jasmine) side of an end-to-end testing setup. It is not unreasonable to interpret things like you have interpreted them, but it doesn't change the fact that the interpretation is wrong.
The ngMockE2E will pass through requests if instructed when used on the server side of an application rather than on the client side. This is meant so that you can still pass through certain requests that are hard to mock as pre-canned responses. What I mean by client and server-side is that in end-to-end testing there are two ends. You have the application to be tested which is served by a standard application server, and you have the test code that is driving the application usually executing in Karma or another test runner, which uses standard HTTP requests to communicate to the application which is executing in another process.
If you look at the documentation and how to setup ngMockE2E you will notice there is no mention of Jasmine, and the instructions are for how to set up in a real angular application:
myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
myAppDev.run(function($httpBackend) {
phones = [{name: 'phone1'}, {name: 'phone2'}];
// returns the current list of phones
$httpBackend.whenGET('/phones').respond(phones);
// adds a new phone to the phones array
$httpBackend.whenPOST('/phones').respond(function(method, url, data) {
phones.push(angular.fromJson(data));
});
$httpBackend.whenGET(/^\/templates\//).passThrough();
//...
});
As you can see in this example, they are mocking all the JSON data instructions, while letting it still fetch the templates from the server.
In order to use it from jasmine the setup would be quite different, using angular.mock.module('ngMockE2E') and then setting up the $httpBackend.whenGET() in a beforeEach() rather than in a module.run().
As far as ngMidwayTester I linked you to, I believe this would, in fact, be compatible with ngMockE2E. Essentially ngMidwayTester replaces angular.mock.module() and inject() with it's own implementations. So you could use it like this:
beforeEach(function(){
tester = ngMidwayTester('app', 'ngMockE2E');
$http = tester.inject('$http');
$httpBackend = tester.inject('$httpBackend');
$rootScope = tester.inject('$rootScope');
});
This should work, because you are no longer using the ngMock module (which always gets included when you use angular.mock.module()). Things should work exactly like you want them to using ngMidwayTester.
To test my app with real backend calls I used a modified version of angular-mocks
It works just like for unit-tests in Jasmine.
I'm using it with Jasmine 2.0, so a test looks like following :
it(' myTest', function (done) {
_myService.apiCall()
.then(function () {
expect(true).toBeTruthy();
done()
});
});
NB: the done is needed because of the async call.
Here is a solution that I use to make real HTTP calls when I'm using ngMock for unit tests. I mainly use it for debugging, trying out the API, getting JSON examples etc.
I wrote a more detailed post about the solution on my blog: How to Unit Test with real HTTP calls using ngMockE2E & passThrough.
The solution is as follows:
angular.mock.http = {};
angular.mock.http.init = function() {
angular.module('ngMock', ['ng', 'ngMockE2E']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
angular.mock.http.reset = function() {
angular.module('ngMock', ['ng']).provider({
$browser: angular.mock.$BrowserProvider,
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
Include this source file after ngMock, for example:
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-mocks.js"></script>
<!-- this would be the source code just provided -->
<script type="text/javascript" src="ngMockHttp.js"></script>
How to write the test?
describe('http tests', function () {
beforeEach(module('moviesApp'));
var $controller;
var $httpBackend;
var $scope;
describe('real http tests', function() {
beforeEach(angular.mock.http.init);
afterEach(angular.mock.http.reset);
beforeEach(inject(function(_$controller_, _$httpBackend_) {
$controller = _$controller_;
$scope = {};
$httpBackend = _$httpBackend_;
// Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
$httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
}));
it('should load default movies (with real http request)', function (done) {
var moviesController = $controller('MovieController', { $scope: $scope });
setTimeout(function() {
expect($scope.movies).not.toEqual([]);
done();
}, 1000);
});
});
});
How it works?
It uses ngMockE2E's version of $httpBackEndProvider, which provides us with the passThrough function we see being used in the test. This does as the name suggests and lets a native HTTP call pass through.
We need to re-define the ngMock module without its fake version of the $BrowserProvider, since that is what prevents the real HTTP calls in unit tests that use ngMock.
Why I do it this way?
I like the flexibility to being able to easily switch between using fakes and real HTTP calls as it helps my workflow when writing the tests, which is why ngMockE2E's version of $httpBackEndProvider is used. It also allows me to code the unit tests in the same way as using ngMock, where I can simply drop in/out the beforeEach/afterEach line to register angular.mock.http.init/reset.
Plunkr
Here's a Plunkr with an example.