Unit testing with ngStorage - angularjs

I'm assigning various values to local storage in our Jasmine unit tests like so:
beforeEach(inject(function ($controller, $injector, $rootScope) {
$localStorage = $injector.get('$localStorage');
$localStorage.webAPIUrl = 'myUrl';
}
it('Can make API call', function() {
var apiCall = $localStorage.webAPIUrl + params;
httpBackend.expectGET(apiCall).respond({});
});
Unfortunately, anything I set in $localStorage is undefined when I try to retrieve it in the application, so tests like this fail because the angular service in the application utilizes $localStorage.webAPIUrl. These tests were fine previously (when we just stored values like this in window.AppConfig), but now we're running into this problem after switching to ngStorage.
Is there a piece of the puzzle I'm missing, or is there a problem with ngStorage and unit testing?

Related

Angular JS Test driven Development with multiple controllers

I have developed an web application using Angular JS. I am getting few additional CR which needs to implemented in using TTD approach. We have return unit test cases using Jasmine and Karma. The challenge currently we face is when we try to write unit test case for multiple controllers. I have a main page return on Home Controller & its has an broadcast event in another controller. When i write a unit test case Object for the controller which has this broadcast event is not initialized.
Is there any way to inject the second controller as a dependent object. Answers with Reference Sample link or demo code is much appreciated.
You state you are using Jasmine and Karma so I assume you are unit testing. If you are "unit" testing you should test each controller individually while mocking, spy, all injected services.
beforeEach(inject(function ($rootScope, $controller) {
rootScope = $rootScope;
scope = $rootScope.$new();
controller = $controller('MyCtrl as ctrl', {
'$scope': scope
});
}));
it('', function(){
//Arrange
controller.counter = 0; // Your controller is listening on scope.$on to update this counter.
//Act
rootScope.$broadcast('xyz', {});
//Assert
expect(controller.counter == 1).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 2).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 3).toBe(true);
});
Just be careful with broadcast. Only a domain events (model updated/deleted/created), or something global (signin,signout) should travel over $broadcast. Otherwise, it should be replaced with a service + directive. An example is angular material https://material.angularjs.org/latest/api/service/$mdDialog that is 1 directive with a backing service that can be open/closed from anywhere.
You can inject any controller with the $controller service, e.g.
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
see docs here:
https://docs.angularjs.org/api/ngMock/service/$controller
so what you do is inject that controller first, then your other controller. then the first controller will have been instantiated at the time the second gets instantiated.
I am new to angular, it seems to be possible to inject multiple controllers at once. However best practice is to generate a mock controller that behaves as you expect the second controller to behave. This reduces the number of things you are testing at once.
The following link may be helpful for creating a mock controller, http://www.powdertothepeople.tv/2014/08/28/Mocking-Controller-Instantiation-In-AngularJS-Unit-Test/ .

Calling angular services globally

I have a set of services which represent a backend logic, that is called by different angular controllers.
During development however I want to call these services directly, in order to test them in isolation, from the browser Javascript console.
How this can be done?
Say there is
app.service('service1', function() {
this.sayHello = function() {
return "Hello"
};
});
Now from Javascript console,
app.somethingToGetService('service1').sayHello()
?
You can get injector for module and from it get service:
angular.injector(['ng', 'myApp']).get('service1').sayHello()

Controller testing fails due to the service dependency injection

When making use of a service in a controller test do you need to initialize the service in the same way you would the controller? By this I mean do you need to pass it its own dependencies?
For example I can initialize my controller like so:
// Instantiate the controller
searchController = $controller( 'VisibilitySearchController',{
$scope: scope,
dataService: dataService
});
}));
so do I need to initialize the service according to the components it needs like $http, $resource etc as well as make spyOn calls on its functions? Or is this/should this be sufficient? (Note - my tests fail when I do the following )
// Instantiate the dataService
dataService = $injector.get( 'dataService' );
it throws this error:
* Error: [$injector:unpr] Unknown provider: $resourceProvider <- $resource <- dataService
The relevant part of the service:
myAppServices.factory('dataService', ['$http', '$resource', 'authService', 'messageService', function ($http, $resource, authService, messageService) {
}
Side note
Note - we are using Maven as our build tool and only make use of Jasmine at this point - trying to bring Karma in as our test-runner as a Maven plugin.
You must provide all the dependencies but you can mock them. This can be done by jasmine like this for example:
var mockedDataService = jasmine.createSpyObj('dataService', ['getData', 'getOtherData']);
And then you inject this mocked service to $provider:
beforeEach(function () {
module(function ($provide) {
$provide.value('dataService', mockedDataService );
});
}
Instance of this mocked service can be retrieved like this then:
inject(function (dataService) {
var dataServiceInstance = dataService;
});
This will provider mocked dataService anytime it is needed. However if you need fully functional dataService you must instantiate it but always you can mock any of its dependecies.
While you can inject dependencies into the controller manually you don't need to do it as long as you have loaded the module the service belongs to.
In your case it looks like you have not loaded the ngResource module.
If you add beforeEach(module('ngResource')) to your test (and make sure the actual script file it lives in is included in Jasmine's fileset) you should not need to inject it manually.
Note that you do not need to load angular core services like $http, but since $resource is not part of core it needs to be loaded like this.
Injecting dependencies manually is mostly useful if you want to provide a mock implementation.

Angular testing with qunit and httpBackend

I have bee struggling with this all day. Here is my test
injector = angular.injector(['ngMock','ng', 'cockpit']);
var equal = QUnit.assert.equal;
test('loginService', function () {
var app, service, scope, httpBackend;
app = angular.module('cockpit');
app.config(function ($provide) {
$provide.decorator('httpBackend',
angular.mock.e2e.$httpBackendDecorator);
});
httpBackend = injector.get('$httpBackend');
httpBackend.when("PUT", "/login").respond({ userId: 23 });
service = injector.get('loginService');
service.$http = httpBackend;
service.getUserId('easy', 'path');
httpBackend.flush();
equal(service.userId, 23, 'populates userId property');
});
Inside the getUserId method the $http service has not put method, so when I make the $http.put call, it fails. I must be setting the test up incorrectly.
I did not get that $httpBackend in Angular mocks is a wrapper for the $httpBackend in angular itself. You just have to instantiate it and set it up to intercept the $http calls the way you want. That said, I still can't get $httpBackend.flush() to work. Testing in angular isn't quite as easy as advertised.
$http and $httpBackend are two different services. $http uses $httpBackend. $http is the service which has the put() method.
So, the following line doesn't make sense:
service.$http = httpBackend;

Mocking collaborators in Angular controller and service Jasmine tests

I have been writing some Jasmine unit tests in Angular. In the first example I'm testing a controller.
myApp.controller('MyCtrl', function($scope, Config){
...
});
I have a configuration service (Config) that keeps configuration from the database and is injected into my controller. As this is a unit test, I want to mock out that configuration service altogether, rather than allowing execution to pass through it and using $httpBackend. Examples I found taught me about a $controller function I can use like this, in order to get an instance of my controller with my mocks injected in place of the usual collaborator:
beforeEach(inject(function($controller, $rootScope){
var scope = $rootScope.$new();
var configMock = {
theOnlyPropertyMyControllerNeeds: 'value'
};
ctrl = $controller('MyCtrl', {
$scope:scope,
Config: configMock
});
}));
But I also have other services that use the Config service. To help unit test them, I assumed there would be a similar $service function I could use to instantiate a service with whatever mocks I want to provide. There isn't. I tried $injector.get, but it doesn't seem to let me pass in my mocks. After searching for a while, the best I could come up with in order to instantiate a service in isolation (avoid instantiating its collaborators) is this:
beforeEach(function() {
mockConfig = {
thePropertyMyServiceUses: 'value'
};
module(function($provide) {
$provide.value('Config', mockConfig);
});
inject(function($injector) {
myService = $injector.get('MyService');
});
});
Is this the right way? It seems to be overriding the entire application's definition of the Config service, which seems maybe like overkill.
Is it the only way? Why is there no $service helper method?
For unit testing, it is common that you override a service for the sake of testing. However, you can use $provide to override an existing service instead of using inject, as long as you load the application before hand.
Assuming that you created Config using something like:
angular.moduel('...', [...]).factory('Config', function (...) {...});
If so, try this:
...
beforeEach(module("<Name of you App>"));
beforeEach(
module(function ($provide) {
$provide.factory('Config', function (...) {...});
});
);
...
After that, when you initialise your controller, it will get the mocked Config.

Resources