AngularJS Jasmine Unit Tests - javascript

I have the following unit tests, and for some reason the second test makes other tests fail.
beforeEach(inject(function ($rootScope, _$httpBackend_, $controller, $location, mockedResource) {
scope = $rootScope.$new();
httpBackend = _$httpBackend_;
locationService = $location;
ctrlDependencies = {
$scope: scope,
resource: mockedResource,
}
var ctrl = $controller('myController', ctrlDependencies);
}));
it('should redirect to a new page', function() {
scope.pageRedirectFunction();
expect(locationService.path()).toBe('/newpage')
});
it('should delete an epic resource', function() {
httpBackend.expectGET('/api/v1/epic/1').respond({});
httpBackend.expectDELETE('/api/v1/epic/1').respond({});
// Run the deletion function
scope.deleteEpicResource()
httpBackend.flush() // This line seems to be the rebelious one
expect(scope.epicResources.length).toEqual(0)
})
I have managed to figure out the line that seems to cause the errors, and it's the httpBackend.flush() line. Why is the flush function causing strange behaviour?
The actual error I get from running the command karma start in the terminal, is:
Delaying execution, these browsers are not ready: Chrome 29.0 ....
after a little while, the Chrome session then crashes.

A little-known tip about testing/mocking async requests with jasmine and AngularJS:
If you're not explicitly calling the request in your test (i.e. calling it through another function), the request won't be digested by Angular, so it makes it seem as if the request never fired (when you call flush())
Try running scope.$digest() before your httpBackend.flush() call, that may do the trick. See this thread for more information.

Do you have angular-mocks.js included before your tests? Also, you may want to try to load ngMocks module:
beforeEach(module("ngMock"));

Related

Unit testing angular service with dependencies

I have the following Jasmine unit test:
describe('myService', function () {
var myService, $q;
// Instantiate the app
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, fileSystemService, $q) {
myService = _myService_;
spyOn(fileSystemService, 'listFiles').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve('mockresult');
return deferred.promise;
});
}));
it('checks the number of outbound files', inject(function ($rootScope) {
var result;
myService.sendOutboundFiles2().then(function (res) {
result = res;
});
$rootScope.$digest();
expect(result).toBe('mockresult');
}));
});
Which tests this very simple service function:
sendOutboundFiles2() {
return fileSystemService.listFiles('Cached/Outbound').then(function(outfiles) {
return outfiles;
})
}
However when the test runs, it fails with a spurious Error: Unexpected request: GET blah\blah\blah.html No more request expected at $httpBackend error but i have no idea why as neither this test nor the service dependencies do anything with $httpBackend.
MORE INFO
If i comment out my existing controller tests, I get this error:
If i add my controller tests back in, I get this error:
So depending on which tests i add or remove, the HTML file in the GET error changes. But all the controller tests run fine. WTF?!?!?!!??!?!!?
The problem is caused by Ionic's keen prefetching of all templates into a cache. No idea why this doesn't occur when testing a controller though. The problem only appears when i was testing a service. Any way, I found this thread: Karma test breaks after using ui-router and the relevant fix is to add this snippets before injecting any dependencies:
beforeEach(module(function($provide) {
$provide.value('$ionicTemplateCache', function(){} );
}));
This stubs out the $ionicTemplateCache and prevents it from trying to preload all ui-router templates into the Ionic cache.

Karma + PhantomJS TypeError: undefined is not an object (evaluating scope.jackpot)

I am still very new to unit testing, and to be honest, there isn't anything that I could even think of testing, but I cannot build my app unless I have at least 1 test case, so I attempted to make the most simple test case I could, on the smallest block of code in the controller, and it doesn't seem to be working.
I believe it's an error in my test case, and not in my controller's code itself, because when I view my app in the browser with grunt serve the console shows no errors.
This is the error it gives me:
PhantomJS 2.1.1 (Linux 0.0.0) Controller: MainCtrl should attach a list of jackpot to the scope FAILED
/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3746:53
forEach#[native code]
forEach#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:323:18
loadModules#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3711:12
createInjector#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3651:22
workFn#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular-mocks/angular-mocks.js:2138:60
TypeError: undefined is not an object (evaluating 'scope.jackpot') in /home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js (line 20)
/home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js:20:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.04 secs / 0.007 secs)
This is my test case:
it('should attach a list of jackpot to the scope', function () {
expect(scope.jackpot.length).toBe(2);
});
And this is the block of code I'm attempting to run the test on:
var countInJackpot = localStorageService.get('jackpot');
$scope.jackpot = countInJackpot || [
{
letter: '$',
prize: '$1,000,000 Cash',
numbers: ['$611A','$612B','$613C','$614D','$615E','$616F','$617G','$618F'],
count: [0,0,0,0,0,0,0,0]
},
{
letter: '?',
prize: '$500,000 Vacation Home',
numbers: ['?619A','?620B','?621C','?622D','?632E','?624F','?625G','?626H'],
count: [0,0,0,0,0,0,0,0]
}
];
For the time being, I really just want to write 1 simple test case, so it will let me build the app. I'm currently studying unit testing, but I still don't feel ready to write more complex test cases on my own. I will save that for later.
I have included the entire contents of the files in a gist for reference, if needed, and I can include the contents of the karma.conf.js if necessary.
My gist
Within your test case, scope should be $scope?
OR
You probably haven't setup your testing environment to load in your controller.
Here is an example of mine on testing a controller... Angular makes the setup a little iffy to learn, But once you understand the flow. It's pretty great :)
I'm going to try and add as many comments to explain each piece as I can... but let me know if your need clarification. You might be using jasmine, but keep in mind, this is mocha, im using the angular mock library loaded in via the karma.conf.
describe('myController', function() {
var $scope,
createController;
// Runs before each test. Re-extantiating the controller we want to test.
beforeEach(inject(function($injector) {
// Get hold of a scope (i.e. the root scope)
$scope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
// Creates the controller instance of our controller.
// We are injecting $scope so we will have access to it
// after the controllers code runs
return $controller('myCtrl', {
'$scope': $scope
});
};
}));
describe('#myFunction', function() {
it('jackpot should contain two objects', function() {
expect($scope.jackpot.length).to.equal(2);
});
});
});
Hope that helped. Here's some of the resources I used to learn :) Good Luck!
https://quickleft.com/blog/angularjs-unit-testing-for-real-though/
http://jaketrent.com/post/run-single-mocha-test/
I would expect you'd want to test both cases of the localStorageService having and not having data. To do so, create a spy for localStorageService (see Spies) and write your tests like this...
'use strict';
describe('Controller: MainCtrl', function () {
var scope, localStorageService, localData;
beforeEach(function() {
localData = {};
module('monopolyApp');
localStorageService = jasmine.createSpyObj('localStorageService', ['get', 'set']);
localStorageService.get.and.callFake(function(key) {
return localData[key];
});
inject(function($rootScope) {
scope = $rootScope.$new();
});
});
it('assigns jackpots from local storage if present', inject(function($controller) {
localData.jackpot = 'whatever, does not matter';
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot).toBe(localData.jackpot);
}));
it('assigns jackpots from default array if none present in local storage', inject(function($controller) {
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot.length).toEqual(2);
// maybe include some other checks like
expect(scope.jackpot[0].letter).toEqual('$');
expect(scope.jackpot[1].letter).toEqual('?');
}));
});

Isolation with Angular on karma jasmine

I have a set of tests that are spread across 2 modules.
The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
The second module has a series of tests and all of them pass when i run only them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.
How can i make jasmine run each test as if they were the only tests?
It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.
Update 1
I have a jsFiddle representing the issue .
The structure of the problem is :
Some test is ran with a mock dependant module
Later another test wants to test the actual mocked module
Since the first moldule was already loaded we can't overwritte it and the test fails.
On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module
It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.
I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.
The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.
In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.
See this updated fiddle that did two changes from your original fiddle.
Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.
Here's a screenshot of what happens in the following scenario:
Module A registers a directive called baz.
Module B depends on module A.
Module B registers a directive called baz.
As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.
Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.
See the attached screenshot below for what your situation looks like.
The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.
Decorating baz
In your beforeEach for testModule, replace the following:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
With this:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
What about the call to angular.module('parent', [])?
You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.
Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.
As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.
Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.
The syntax is as follows:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.

Spyon provider during config phase in angular.js application

I am writing unit tests for an Angular.js application (with karma and jasmine), and I want to test a certain behavior in the CONFIG phase of a module. I would like to confirm that a certain function of a PROVIDER is being called. I thought I could do this with a spy on the provider's method, but gaining access to the provider before the "expect" has proven rather tricky.
Here is some example code:
Module Code (being tested)
var myApp = angular.module('myApp', ['restangular']);
myApp.config(['RestangularProvider', function (RestangularProvider) {
RestangularProvider.setBaseUrl('http://someurl:someport/');
}]);
I've tried various solutions to get a reference to the RestangularProvider and apply a spy to it, and all failed. The closest I was able to get was the code below:
Unit Test Code
describe("Test if setBaseUrl was called", function () {
var RestangularProvider;
beforeEach(module('myApp', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(RestangularProvider, "setBaseUrl").and.callThrough();
}));
it("should call setBaseUrl.", function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
I do actually get the reference to the RestangularProvider, but the "config" function of the module gets called before that, so I think the spy doesn't get set-up.
I did find a post where the author solved a similar situation with a "work around" by testing the configured "service" instead of testing the actual call to the provider's method. In the example above, I would test the Restangular.configuration.baseUrl in my expect instead of testing the actual call to the provider's setBaseUrl method, but this seemed like it would not be adequate in certain situations.
I am rather new to Angular.js so this may simply be a case of being totally clueless as to the whole "testing config phase", so if that's the case, please feel free to set me straight :]
Any suggestions, critiques or pointers?
I finally solved the problem by separating out the module, whose provider I wanted to spy on, into a diferent "beforeEach" block. The altered code is below, but I still would appreciate any comments as to the whole idea of whether or not this is actually an "adequate test".
describe("Test if setBaseUrl was called", function () {
var RestangularProvider;
//Setup the spy.
beforeEach(function () {
module("restangular", function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(_RestangularProvider_, 'setBaseUrl').and.callThrough();
});
});
beforeEach(module('myApp'));
it("should call setBaseUrl.", function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
As described by OP above, you do need to get the provider before calling the module you want to test.
However, there's no need to separate it in two beforeEach blocks. You also must call inject() function (even if you have nothing to inject) at the end of the beforeEach block.
describe('Test if setBaseUrl was called', function () {
var RestangularProvider;
//Setup the spy.
beforeEach(function () {
module('restangular', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(_RestangularProvider_, 'setBaseUrl').and.callThrough();
});
module('myApp');
inject();
});
it('should call setBaseUrl.', function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
Source: http://java.dzone.com/articles/unit-testing-config-and-run

Unit test when loading things at app run with AngularJS

I need my app to run some configuration at runtime vi an HTTP endpoint.
I wrote a simple service to do that:
module.factory('config', function ($http, analytics) {
return {
load: function () {
$http.get('/config').then(function (response) {
analytics.setAccount(response.googleAnalyticsAccount);
});
}
}
});
Next, I call this module in a run block of my app module:
angular.module('app').***.run(function(config) {
config.load();
});
All is working well when the app is running but in my unit tests, I get this error: "Error: Unexpected request: GET /config"
I know what it means but I don't know how to mock it when it happens from a run block.
Thanks for your help
EDIT to add spec
Calling this before each
beforeEach(angular.mock.module('app'));
Tried this to mock $httpBackend:
beforeEach(inject(function($httpBackend) {
$httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount':});
angular.mock.module('app')
$httpBackend.flush();
}));
But got:
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
EDIT since update to AngularJS 1.0.6
Since I've updated to AngularJS 1.0.6, advised by Igor from the Angular team, the issue is gone but now I've now got this one, which sounds more "normal" but I still can't figure out how to make it works.
Error: Injector already created, can not register a module!
I struggled with this error for a little while, but managed to come up with an sensible solution.
What I wanted to achieve is to successfully stub the Service and force a response, on controllers it was possible to use $httpBackend with a request stub or exception before initiating the controller.
In app.run() when you load the module it initialises the object and it's connected Services etc.
I managed to stub the Service using the following example.
describe('Testing App Run', function () {
beforeEach(module('plunker', function ($provide) {
return $provide.decorator('config', function () {
return {
load: function () {
return {};
}
};
});
}));
var $rootScope;
beforeEach(inject(function (_$rootScope_) {
return $rootScope = _$rootScope_;
}));
it("defines a value I previously could not test", function () {
return expect($rootScope.value).toEqual('testing');
});
});
I hope this helps your app.run() testing in the future.
I don't know if you are still looking for an answer to this question. But here is some information that might help.
$injector is a singleton for an application and not for a module. However, angular.injector will actually try to create a new injector for each module (I suppose you have a
beforeEach(module("app"));
at the beginning.
I had the same problem while using Angular, RequireJS, Karma and Jasmine and I figured out two ways to solve it. I created a provider for the injector function as a separate dependency in my tests. For example MyInjectorProvider which provides a singleton instance of $injector.
The other way was to move the following statements:
beforeEach(module("app"));
beforeEach(inject(function($injector){
...
})
inside the test suite description. So here is how it looked before:
define(['services/SignupFormValidator'], function(validator){
var validator;
beforeEach(module("app"));
beforeEach(inject(function($injector){
validator = $injector.get("SignupFormValidator");
})
describe("Signup Validation Tests", function(){
it("...", function(){...});
});
});
After applying the fix it looks like this:
define(['services/SignupFormValidator'], function(validator){
var validator;
describe("Signup Validation Tests", function(){
beforeEach(module("app"));
beforeEach(inject(function($injector){
validator = $injector.get("SignupFormValidator");
});
it("...", function(){...});
});
});
Both the solutions worked in my case.
You should mock every HTTP request with ngMock.$httpBackend. Also, here is a guide.
Update
You don't need the angular.mock.module thing, just need to inject your app module. Something like this:
var httpBackend;
beforeEach(module('app'));
beforeEach(inject(function($httpBackend) {
httpBackend = $httpBackend;
$httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount': 'something'});
}));
In your tests, when you need the mocked http to answer, you will call httpBackend.flush(). This is why we have a reference to it, so you don't need to inject it in every single test you have.
Note you will need to load angular-mock.js in order to it work.

Categories

Resources