The problem I'm trying to solve is the ability to test my factory using Jasmine.
Below is a copy of my app and factory:
var app = angular.module('app', []);
app.factory('service', function ($http) {
return {
getCustomers: function (callback) {
$http.get('/Home/Customers').success(callback);
},
getProfile: function (callback, viewModel) {
$http.post('/Home/Profiles', JSON.stringify(viewModel), {
headers: {
'Content-Type': 'application/json'
}
}).success(callback);
}
};
});
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
I have also setup jasmine but I'm having trouble testing the above "getCustomers" and "getProfile".
Below is my current attempt:
describe("getCustomers", function (service) {
beforeEach(module('service'));
describe('getCustomers', function () {
it("should return a list of customers", inject(function(getCustomers){
expect(getCustomers.results).toEqual(["david", "James", "Sam"]);
}))
})
});
This would be really helpful if someone could provide an example of how to test both "getCustomers" and "getProfile" in two separete tests.
Kind regards.
You can mock the Http GET request and test the service like this
describe("getCustomers", function (service) {
beforeEach(module('app'));
var service, httpBackend;
beforeEach(function () {
angular.mock.inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
})
});
describe('getCustomers', function () {
it("should return a list of customers", inject(function () {
httpBackend.expectGET('/Home/Customers').respond(['david', 'James', 'Sam']);
service.getCustomers(function (result) {
expect(result).toEqual(["david", "James", "Sam"]);
});
httpBackend.flush();
}))
})
});
Working Demo
Related
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;
}));
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.
I have tried to write a unit test case for post method in angular service. I got $http is undefined error. below is my code. any one tell me what i am missing.
i am adding module using separate file.
service code
sample.factory('AddProductTypeService', function () {
return {
exciteText: function (msg) {
return msg + '!!!'
},
saveProductType: function (productType) {
var result = $http({
url: "/Home/AddProductTypes",
method: "POST",
data: { productType: productType }
}).then(function (res) {
return res;
});
return result;
}
};
});
Jasmine
describe("AddProductTypeService UnitTests", function () {
var $rootScope, $scope, $factory, $httpBackend, basicService,createController, authRequestHandler;
beforeEach(function () {
module('sampleApp');
inject(function ($injector) {
basicService = $injector.get('AddProductTypeService');
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
});
});
// check to see if it does what it's supposed to do.
it('should make text exciting', function () {
var result = basicService.exciteText('bar');
expect(result).toEqual('bar!!!');
});
it('should invoke service with right paramaeters', function () {
$httpBackend.expectPOST('Home/AddProductTypes', {
"productType": "testUser"
}).respond({});
basicService.saveProductType('productType');
$httpBackend.flush();
});
});
error :
ReferenceError: $http is not defined
Thanks in advance
You have to inject the $http service into your service
sample.factory('AddProductTypeService', ['$http' ,function ($http) {
/* ... */
}]);
https://docs.angularjs.org/guide/di
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({});
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.