Unit test inject dependency like controller for service in angularJS - javascript

Lets see, we have this according to:https://docs.angularjs.org/guide/unit-testing
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('PasswordController', { $scope: $scope });
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
now i am making service and factory, is there any equivalent to ____$controller____ for service and factory? so i can inject it with something else like:
var controller = $controller('PasswordController', { $scope: $scope });
and change the inner functions of the dependency so i can test it, or is there any better approach?
Edit: to make question more clear
here is the example of the question:
i have this:
var app = angular.module("app").service("MyService",["$scope","$http",function($scope,$http){
this.myFunction = function(){
$http.get("/myApi/1");
}
}]);
how do i use the equivalent of
var controller = $controller('PasswordController', { $scope: $scope });
so i can inject $scope and $http with something else to myService?

You can't inject dependencies to factories or services on the go, but you can mock the dependencies with your custom objects and have angular substitute them automatically. You can use $provide for that. Here is an example:
angular.module('app').service('some', function(dependencyService) {
});
When testing:
beforeEach(module(function($provide) {
$provide.value('dependencyService', {
});
}));

After doing a workarround, i found out from https://www.sitepoint.com/unit-testing-angularjs-services-controllers-providers/ about the service. i tested out the tutorial here and here is the test script:
(function () {
angular.module('services', [])
.service('sampleSvc', ['$window', 'modalSvc', function ($window, modalSvc) {
this.showDialog = function (message, title) {
if (title) {
modalSvc.showModalDialog({
title: title,
message: message
});
} else {
$window.alert(message);
}
};
}]);
describe("Testing service", function () {
var mockWindow, mockModalSvc, sampleSvcObj;
beforeEach(module(function ($provide) {
$provide.service('$window', function () {
this.alert = jasmine.createSpy('alert');
});
$provide.service('modalSvc', function () {
this.showModalDialog = jasmine.createSpy('showModalDialog');
});
}, 'services'));
beforeEach(inject(function ($window, modalSvc, sampleSvc) {
mockWindow = $window;
mockModalSvc = modalSvc;
sampleSvcObj = sampleSvc;
}));
it('should show alert when title is not passed into showDialog', function () {
var message = "Some message";
sampleSvcObj.showDialog(message);
expect(mockWindow.alert).toHaveBeenCalledWith(message);
expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled();
});
it('should show modal when title is passed into showDialog', function () {
var message = "Some message";
var title = "Some title";
sampleSvcObj.showDialog(message, title);
expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({
message: message,
title: title
});
expect(mockWindow.alert).not.toHaveBeenCalled();
});
});
})();
and i try my own test script:
(function () {
describe("Testing service", function () {
var mockHttp, mockCookies, mockApi;
beforeEach(function () {
module(function ($provide) {
$provide.service('$http', function () {
this.defaults = {
headers: {
common: {
}
}
};
});
$provide.service('$cookies', function () {
});
});
module('timesheet');
});
beforeEach(inject(function ($http, $cookies, APIService) {
mockHttp = $http;
mockCookies = $cookies;
mockApi = APIService;
}));
it('Test Service', function () {
});
});
})();
apparently in somewhere in my code, there is an app.run which inside do the
$http.defaults.headers.common.Authorization = 'Bearer ' + $cookies.get('sessionToken');
and causes the error the moment i inject the $http with something else because headers not defined, i thought it was from my own test script because they are using same name, but apparently this is the one causing problem.
So, actually the moment we load in testing mode, the angularjs still do the whole running of application, in which i forgot about this one.

Related

AngularJS Jasmine - Test with different Mock Response in next 'it' block

describe('test mock', function () {
var resource, employeeRoles, provider, mockBaseUrl, mockUser, mockOffices, mockCalendar, $window, $httpBackend, $scope, $serializer;
beforeEach(angular.mock.module('appointmentManager'));
beforeEach(inject(function ($injector, $rootScope, $httpParamSerializer) {
mockCalendar = {
// sample data
};
$window = $injector.get('$window');
$window.ApiBaseUrl = mockBaseUrl;
$window.LoggedInUser = mockUser;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.expectPOST('api/Calendar/GetCalendar').respond(200, mockCalendar);
$scope.$apply();
$httpBackend.flush();
}));
it('should be defined in module', function () {
expect(resource).toBeDefined();
expect(provider).toBeDefined();
});
it('active employees should be true', function () {
expect(provider.isNoActiveEmployeesAvailable).toBe(false);
//test with another mockCalendar data
});
afterEach(function () {
$scope.$destroy();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
I want to test with different Mock Response in Next it block. Here, how can I assign new value in mockCalendar variable before the second it block runs?
In your scenario, which is not uncommon, I typically create a function that has code that is going to be common in all my tests, and then call it from your test, after additional arrangements that cannot be performed in the beforeEach. Note this is different than using the beforeEach, because you still need to be able to modify something prior to calling the function.
For instance, your code would be modified to be like the following:
describe('test mock', function () {
var resource, employeeRoles, provider, mockBaseUrl, mockUser, mockOffices, mockCalendar, $window, $httpBackend, $scope, $serializer;
var setupTest = function(mockCalendar) {
$httpBackend.expectPOST('api/Calendar/GetCalendar').respond(200, mockCalendar);
$scope.$apply();
$httpBackend.flush();
};
beforeEach(angular.mock.module('appointmentManager'));
beforeEach(inject(function ($injector, $rootScope, $httpParamSerializer) {
mockCalendar = {
// sample data
};
$window = $injector.get('$window');
$window.ApiBaseUrl = mockBaseUrl;
$window.LoggedInUser = mockUser;
$httpBackend = $injector.get('$httpBackend');
}));
it('should be defined in module', function () {
setupTest(mockCalendar); // not sure if this is needed here.
expect(resource).toBeDefined();
expect(provider).toBeDefined();
});
it('should set provider.isNoActiveEmployeesAvailable to false when xxx', function () {
mockCalendar.someField = 'some value';
setupTest(mockCalendar);
expect(provider.isNoActiveEmployeesAvailable).toBe(false);
});
it('should do something else', function () {
mockCalendar.someField = 'some other value';
setupTest(mockCalendar);
expect(provider.isNoActiveEmployeesAvailable).toBe(true);
});
afterEach(function () {
$scope.$destroy();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});

Issue with jasmine spies call through

I am having trouble calling through to the actual implementation and I am getting this error:
TypeError: undefined is not an object (evaluating 'GitUser.GetGitUser('test').then') ...
Here are my codes:
app.controller('HomeController', ['$scope', 'GitUser', function ($scope, GitUser) {
$scope.name = "user";
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
}]);
app.factory('GitUser', function ($http) {
return {
GetGitUser: function (username) {
return $http.get('https://api.github.com/users/' + username)
.then(function success(response) {
return response.data.login;
});
}
};
});
Here is my unit test:
describe('HomeController Unit Test', function () {
var $controllerConstructor, scope;
beforeEach(module("AngularApp"));
beforeEach(inject(function ($controller, $rootScope) {
$controllerConstructor = $controller;
scope = $rootScope.$new();
}));
it('should test if scope.name is test', function () {
// Act
GitUser = {
GetGitUser: function () { }
};
spyOn(GitUser, "GetGitUser").and.callThrough();
GitUser.GetGitUser();
$controllerConstructor('HomeController', {
'$scope': scope,
'GitUser': GitUser
})
// Assert
expect(GitUser.GetGitUser).toHaveBeenCalled();
expect(scope.name).toBe('test');
});
});
The problem is a bit more complex than just a missing inject ...
Here's an adjusted test:
https://plnkr.co/edit/ZMr0J4jmLPtDXKpRvGBm?p=preview
There are a few problems:
1) you are testing a function that returns a promise - so you need to also mock it that way (by using return $q.when(..) for example).
2) you are trying to test code that happens when your controller is created - the
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
should be wrapped in a function instead:
function init() {
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
}
and then make that available on your scope:
scope.init= init;
Then in your test call the function and verify your assertions. If you don't wrap it in a function it won't be testable.
Also - the mocking and the callThrough thing ... as you are testing the controller (and not the service) you can use callFake instead - the callFake function can return a Promise with a value (the one that you want to verify later) - then you can ensure that the controller part of the puzzle works.
var name = 'test';
// instead of trying to mock GitUser you can just callFake and be sure to return a promise
spyOn(GitUser, "GetGitUser").and.callFake(function() {
return $q.when(name);
});
I hope this all makes sense - the plunker should make things clear - I will add some more comments there.
I think you just miss something here
beforeEach(inject(function ($controller, $rootScope, _GitUser) {
$controllerConstructor = $controller;
scope = $rootScope.$new();
GitUser = _GitUser;
}));

$scopeProvider <- $scope/ Unknown provider

I testing my angular-application with jasmine(http://jasmine.github.io/2.0/) and getting next error:
Unknown provider: $scopeProvider <- $scope
I know, that it's incorrect to build dependency with scope in filters, services, factories, etc., but I use $scope in controller!
Why am i getting this error? controller looks like
testModule.controller('TestCont', ['$filter', '$scope', function($filter, $scope){
var doPrivateShit = function(){
console.log(10);
};
this.lol = function(){
doPrivateShit();
};
this.add = function(a, b){
return a+b;
};
this.upper = function(a){
return $filter('uppercase')(a);
}
$scope.a = this.add(1,2);
$scope.test = 10;
$scope.search = {
};
}]);
and my test's code:
'use strict';
describe('testModule module', function(){
beforeEach(function(){
module('testModule');
});
it('should uppercase correctly', inject(function($controller){
var testCont = $controller('TestCont');
expect(testCont.upper('lol')).toEqual('LOL');
expect(testCont.upper('jumpEr')).toEqual('JUMPER');
expect(testCont.upper('123azaza')).toEqual('123AZAZA');
expect(testCont.upper('111')).toEqual('111');
}));
});
You need to manually pass in a $scope to your controller:
describe('testModule module', function() {
beforeEach(module('testModule'));
describe('test controller', function() {
var scope, testCont;
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
testCont = $controller('TestCont', {$scope: scope});
}));
it('should uppercase correctly', function() {
expect(testCont.upper('lol')).toEqual('LOL');
expect(testCont.upper('jumpEr')).toEqual('JUMPER');
...
});
});
});
Normally, a $scope will be available as an injectable param only when the controller is attached to the DOM.
You need to associate somehow the controller to the DOM (I'm mot familiar with jasmine at all).
I am following a video tutorial from egghead (link bellow) which suggest this approach:
describe("hello world", function () {
var appCtrl;
beforeEach(module("app"))
beforeEach(inject(function ($controller) {
appCtrl = $controller("AppCtrl");
}))
describe("AppCtrl", function () {
it("should have a message of hello", function () {
expect(appCtrl.message).toBe("Hello")
})
})
})
Controller:
var app = angular.module("app", []);
app.controller("AppCtrl", function () {
this.message = "Hello";
});
I am posting it because in the answer selected we are creating a new scope. This means we cannot test the controller's scope vars, no?
link to video tutorial (1min) :
https://egghead.io/lessons/angularjs-testing-a-controller

AngularJS Mocking $http returns Error

I am having a bit of trouble testing a HTTP POST in AngularJs with Jasmine.
I have a controller that looks like so:-
appControllers.controller("TaskAddController", function ($scope, $http) {
$scope.task = {};
$scope.messages = {};
$scope.actions = {
save : function() {
$http.post("/ajax/tasks/save", $scope.task)
.then(function() {
$scope.messages.success = true;
$scope.task = {};
});
}
};
});
I am testing it like so:-
describe("TaskAddController", function() {
var createController, scope, $httpBackend;
beforeEach(function () {
module('appControllers');
scope = {};
inject(function ($injector) {
$httpBackend = $injector.get("$httpBackend");
});
inject(function ($controller) {
createController = function () {
return $controller("TaskAddController", { $scope: scope });
};
});
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it("when actions.save is called then should call service", function () {
var task = {
title: "Title",
description: "Description"
};
$httpBackend.expectPOST("/ajax/tasks/save", task);
createController();
scope.task = task;
scope.actions.save();
$httpBackend.flush();
});
});
This causes me to get the following error Error: No pending request to flush !
What am I doing wrong?
Thanks.
What version of AngularJS are you using?
When I run the code I get: Error: No response defined !
When I add a response the test passes:
$httpBackend.expectPOST("/ajax/tasks/save", task).respond({});

Difference between "angualar.module" and "module" when unit-testing Angular

What is the difference between angual.module('app') and module('app')?
Here is the simple service and unit test in question:
Service
(function () {
"use strict"
var app = angular.module('app', []);
app.service('CustomerService', ['$http', function ($http) {
return {
getById: function (customerId) {
return $http.get('/Customer/' + customerId);
}
}
}]);
}());
Test
describe('Customer Service', function () {
var $rootScope,
$httpBackend,
service,
customerId = 1;
beforeEach(function () {
angular.module('app', ['ngMock']);
inject(function ($injector) {
$rootScope = $injector.get('$rootScope');
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('/Customer/' + customerId).respond({ id: customerId, firstName: 'Joe', lastName: 'Blow' });
service = $injector.get('CustomerService');
});
});
afterEach(function () {
$httpBackend.verifyNoOutstandingRequest();
});
it('should get customer by id', function () {
var customer;
service.getById(1).then(function (response) {
customer = response.data;
});
$httpBackend.flush();
expect(customer.firstName).toBe('Sam');
});
});
module in the unit test framework refers to the mock angular.mock.module method (which is attached to window as a convenience). angular.module is the method that angular.mock.module mocks.

Categories