Provide a dynamic value for Angular Constant in Jasmine test - javascript

I have a constant which is injected into a controller and I need to write a test which changes this constant and expects different results. I can use $provide to mock the constant but according to articles I've found online, I need to do it in the module declaration, which I believe is like this:
beforeEach(module("someModule"));
beforeEach(function () {
module(function ($provide) {
$provide.constant('someConstant', false);
});
});
I later load the controller like this:
function createController() {
view = $controller(
"someController",
{
$scope: $injector.get("$rootScope").$new()
});
}
Where $controller, $scope and $injector are all injected in my main beforeEach
This does provide the constant and it does change if I change the value in my beforeEach. But only for the entire test suite. I want to change this constant in a describe or an it but I'm not sure how. If I move the $provide down to a describe or it, I get the error:
Error: Injector already created, can not register a module!
I could just create a new file and that is probably what I'm going to do but is there a way I can $provide a dynamic value?

Lets consider such a code
controller
angular.module('someModule', [])
.controller('someController', function($scope, someConstant) {
$scope.someProvidedValue = someConstant;
})
and test for it
controller spec
describe('module', function () {
beforeEach(module("someModule"));
var createController;
beforeEach(inject(function (_$controller_, _$injector_) {
scope = _$injector_.get("$rootScope").$new()
createController = function createController(scope, obj) {
_$controller_("someController", {
$scope: scope,
someConstant: obj.someConstant
});
}
}))
it('someConstant', function () {
expect(scope.someProvidedValue).toBe(undefined)
createController(scope, {
someConstant: false
})
expect(scope.someProvidedValue).toBe(false)
})
it('someConstant', function () {
expect(scope.someProvidedValue).toBe(undefined)
createController(scope, {
someConstant: true
})
expect(scope.someProvidedValue).toBe(true)
})
})
In the mean time I'm looking for looks nicer solution.

Related

AngularJS tests - inject -> module -> inject

I'm trying to test a service documentViewer that depends on some other service authService
angular
.module('someModule')
.service('documentViewer', DocumentViewer);
/* #ngInject */
function DocumentViewer($q, authService) {
// ...
this.view = function(doc) {
//...
}
}
This is what my test looks like at the moment
it('test', inject(function($q) {
var doc = {
view: function() {
return $q.resolve(0);
}
};
var auth = {
refreshProfileData: function() {
return $q.resolve(0);
},
};
var viewer = createViewer(auth);
}));
function createViewer(auth) {
var viewer;
module({
authService: auth
});
inject(function(documentViewer) {
viewer = documentViewer;
});
return viewer;
}
The problem is I need to call inject to grab a $q, then use it to create my mocks, register my mocks with module, and then call inject again to grab the unit under test.
This results in
Error: Injector already created, can not register a module! in bower_components/angular-mocks/angular-mocks.js (line 2278)
I've seen lots of answers here on SO saying you can't call module after inject, but they don't offer any alternative to a scenario like the above.
What's the correct approach here?
PS: I'd like to avoid using beforeEach, I want each test to be self-contained.
module is used to define which modules will be loaded with inject and cannot be called after inject, this is chicken-egg situation.
The object accepted by module is used to define mocked services with $provide.value:
If an object literal is passed each key-value pair will be registered on the module via $provide.value, the key being the string name (or token) to associate with the value on the injector.
There can be no more than 1 function like createViewer that calls both module and inject. If this means that this kind of self-contained test is an antipattern, there is nothing that can be done about that. Angular testing works best with usual habits, including beforeEach and local variables.
In order to eliminate the dependency on $q, mocked service can be made a factory.
it('test', function () {
var authFactory = function ($q) {
return {
refreshProfileData: function() {
return $q.resolve(0);
},
};
};
// mocks defined first
module(function ($provide) {
$provide.factory('authService': authFactory);
});
var viewer;
inject(function(documentViewer) {
viewer = documentViewer;
});
// no module(...) is allowed after this point
var $q;
inject(function(_$q_) {
$q = _$q_;
});
var doc = {
view: function() {
return $q.resolve(0);
}
};
});

Karma error - Expected undefined to be defined

I want to unit test my controller. I started with basic test assertions of expect API. But I am facing challenge in mocking scope methods inside a conditional check. I am getting an undefined error since it is not available under scope, only the global logout() method is available.
I tried mocking the localStorageService using spyOn as true to satisfy the condition, but that's still of no help. Any solution will be of great help to get me kickstarted.
Controller:
angular.module('app').controller('sampleCtrl',
function($scope, $state, $http, $rootScope, localStorageService) {
if (!(localStorageService.get('isAuthenticated'))) {
$state.go('home');
}
if (localStorageService.get('isAuthenticated') === true) {
//http post calls made here to perform certain operation on page load
$scope.someMethod = function(){
//do something
}
}
$scope.logOut = function() {
localStorageService.set('property', '');
localStorageService.set('isAuthenticated', false);
$state.go('home');
};
});
Karma:
'use strict';
describe('Controller: sampleCtrl', function() {
/** to load the controller's module */
beforeEach(module('app'));
var sampleCtrl,scope,httpBackend,deferred,rootScope;
beforeEach(inject(function ($controller,_$rootScope_,$httpBackend,$q) {
var store = {};
scope= _$rootScope_.$new(); // creates a new child scope of $rootScope for each test case
rootScope = _$rootScope_;
localStorageService = _localStorageService_;
httpBackend = $httpBackend;
httpBackend.whenGET(/\.html$/).respond('');
spyOn(localStorageService, 'set').and.callFake(function (key,val) {
store[key]=val;
});
spyOn(localStorageService, 'get').and.callFake(function(key) {
return store[key];
});
sampleCtrl = $controller('sampleCtrl',{
_$rootScope_:rootScope,
$scope:scope,
$httpBackend:httpBackend,
_localStorageService_:localStorageService
// add mocks here
});
localStorageService.set('isAuthenticated',true);
}));
/**ensures $httpBackend doesn’t have any outstanding expectations or requests after each test*/
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('sampleCtrl to be defined:',function(){
httpBackend.flush();
expect(sampleCtrl).toBeDefined();
});
// failing test case - scope.someMethod not available in scope
it('is to ensure only authenticated user can access the state methods',function(){
localStorageService.get('isAuthenticated');
httpBackend.flush();
expect(scope.someMethod).toBeDefined();
});
});
I've managed to get it work.
The problem was that localStorageService did not have isAuthenticated set to true on starting the controller. Place setting it to true before calling the controller.

Mocking custom provider injected into provider when unit testing Angular in Jasmine

I'm unit testing a provider in Jasmine, which relies on another provider. There's no configuration associated with this provider. When mocking a provider, I've read you're supposed to use something like
beforeEach(module(function ($provide) {
mockInjectedProvider = { };
$provide.value('injected', mockInjectedProvider );
}));
which works fine for me when injecting a custom provider into a service. When injecting them into a provider it doesn't work though. The code doesn't fail, but what gets executed when testing is the actual provider, not the mocked one. Abstracted example below.
var mockInjectedProvider;
beforeEach(function () {
module('myModule');
});
beforeEach(module(function ($provide) {
mockInjectedProvider = {
myFunc: function() {
return "testvalue"
}
}
};
$provide.value('injected', mockInjectedProvider );
}));
beforeEach(inject(function (_base_) {
baseProvider = _base_;
}));
it("injectedProvider should be mocked", function () {
var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
expect(resultFromMockedProvider).toEqual("testvalue");
}); // Here instead of using my mock it executes the actual dependency
In the $provide.value statement I've tried including both injected and injectedProvider, as well as using $provide.provider and mocking a $get function on it but nothing seems to work. I just can't get it to mock away the actual provider. Abstracted base provider looks like this.
(function (ng, module) {
module.provider("base",
["injectedProvider", function (injectedProvider) {
this.executeMyFuncFromInjected= function() {
return injectedProvider.myFunc(); // let's say this returns "realvalue"
}
this.$get = function () {
return this;
};
}]
);
})(window.angular, window.angular.module("myModule"));
Everything in my code is working except the Jasmine mocking.
In this case is better to just mock the return value instead of the provider.
var mockInjectedProvider;
beforeEach(function () {
module('myModule');
});
beforeEach(inject(function (_injected_) {
spyOn(_injected_, "myFunc").and.returnValue("testvalue");
}));
beforeEach(inject(function (_base_) {
baseProvider = _base_;
}));
it("injectedProvider should be mocked", function () {
var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
expect(resultFromMockedProvider).toEqual("testvalue");
}); // Here instead of using my mock it executes the actual dependency

Reduce boilerplate for testing angularjs controllers

In my angular app, I have around 30 controllers and 1 test file for each of them. Those test files always begin with something like this boilerplate:
'use strict';
describe('Controller: SomeController', function ()
{
var controller;
var scope;
beforeEach(function ()
{
module('SomeControllerModule');
inject(function ($controller, $rootScope)
{
scope = $rootScope.$new();
controller = $controller('SomeController',
{
$scope: scope
});
});
});
it('should prepare controller scope', function ()
{
console.log('scope', scope);
});
});
Is there a way to somehow make it shorter, so that I don't have to repeat it in each of my files?
There's ng-describe which looks like it could be very useful (I haven't used it personally yet). With that your code becomes something like:
ngDescribe({
modules: 'SomeControllerModule',
controllers: 'controller',
tests: function (deps) {
it('should prepare controller scope', function () {
console.log('scope', deps.controller);
});
}
});
Unfortunately they don't support jasmine at the moment which probably rules it out for a lot of people.
Yes, I have a cleaner way of doing it:
describe('HomeController', function() {
var $scope;
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('HomeController', { $scope: $scope });
}));
it('true should be truthy', function() {
expect(true).toBeTruthy();
});
});
Also check out this AngularJS Scaffolding that comes with all nuts and bolts you might need in your AngularJS project.

Unit testing AngularJS lazy-loaded controller

I am using this strategy to lazy-load stuff with RequireJS in my AngularJS app:
define([
'src/services/dependency_resolver', // resolves promise when dependencies are `require`d
'json!modules.json'
], function (dependencyResolver, modules) {
var app = angular.module('myApp', [ 'ngRoute' ]);
app.config(function ($controllerProvider, $routeProvider) {
app.lazy = {
controller: $controllerProvider.register
// <...> other providers
};
angular.forEach(modules, function (moduleConfig) {
angular.forEach(moduleConfig.routes, function (route) {
$routeProvider.when(route.path, {
templateUrl: route.templateUrl,
controller: route.controller,
resolve: dependencyResolver(moduleConfig.dependencies)
});
});
});
});
return app;
});
But I'm not sure what is the correct way test a lazy-loaded controller. It is registered like this:
define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});
And this is my current spec:
describe('`MainCtrl` controller', function () {
var Ctrl,
$scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(function (done) {
require(['module/main'], done);
});
beforeEach(function () {
angular.mock.inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
Ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
it('should ...', function () {
console.log(Ctrl);
});
});
With this spec, an error occurs when controller is being registered, because app.lazy is undefined.
So the question is how to test such controllers?
Cheers!
I was experiencing a similar problem when writing my unit test using the "lazy" property to register my controller. The problem with this approach is that when in the context of a unit test, the module config block will not be executed and as a result, app.lazy will resolve to undefined.
To solve your problem, instead of using provider registration methods to set your properties of app.lazy, the provider registration method should be used to override their counterparts on the module. In other words, your config block should now become:
`app.config(function ($controllerProvider, $routeProvider) {
app.controller = $controllerProvider.register
// <...> other providers
.......
}`
Instead of register your controller using (app.lazy):
`define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});`
you can just define like this:
`define(['src/app'], function (app) {
app.controller('MainCtrl', function () {
//
});
});`
And this should work! Hopefully this can help, and please let me know if this works out or not.
First of all, thank you for the reference you provided - the article is really interesting.
Author of the article is using AngularJs providers to implement his strategy. The thing is, that AngularJs doesn't have providers for 'specs'. So my opinion is that you should omit this strategy in your unit tests.
On this basis, I think, that you should add AMD to your spec file. Define your controller as a dependency in your spec. After this, you may just require all your specs somewhere in main-spec.js and launch your testing framework.

Categories