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({});
Related
I have simple factory in AngularJS:
function usersListDataProviderFactory(UserResource, $q) {
var usersListDataProvider = {},
queryParams = {};
init(queryParams);
return {
initDataProvider: function (queryParams) {
init(queryParams);
},
getDataProviderPromise: function () {
return usersListDataProvider;
}
};
function init(queryParams) {
var defer = $q.defer();
UserResource.getUsers(queryParams).then(function (response) {
defer.resolve(response);
}, function (error) {
defer.reject(error);
});
usersListDataProvider = defer.promise;
}
}
I have written tests in Karma / Jasmine that pass after commenting the lines:
init(queryParams);
When I restore an automatic function call I get a message:
TypeError: undefined is not a function (evaluating 'UserResource.getUsers(queryParams).then')
I know the problem is the configuration of the test and the moment the spy was created, but I have no idea how to solve the problem. Current test configuration:
describe('Service: UsersListDataProvider', function () {
var usersListDataProvider,
userResourceStub,
deferred,
$rootScope,
$q;
beforeEach(function () {
module('UsersList');
});
beforeEach(function () {
userResourceStub = {
getUsers: function (queryParams) {
return queryParams;
}
};
module(function ($provide) {
$provide.value('UserResource', userResourceStub);
});
});
beforeEach(inject(function (_UsersListDataProvider_, _$rootScope_, _$q_) {
usersListDataProvider = _UsersListDataProvider_;
$rootScope = _$rootScope_;
$q = _$q_;
deferred = _$q_.defer();
}));
beforeEach(function () {
spyOn(userResourceStub, 'getUsers').and.returnValue(deferred.promise);
});
describe('Method: initDataProvider', function () {
it('');
});
});
Any idea ?
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();
});
});
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.
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.
Trying to get some units tests in AngularJS (using jasmine & karma) working and struggling to comprehend dependency injection... current error message in karma reads 'Error: Argument 'fn' is not a function, got string'
app.js
angular.module('App', [ 'App.Services', 'App.Controllers', 'App.Directives']);
controller.js
angular.module('App.Controllers', []).
controller('MarketplaceCtrl', function ($scope, apiCall) {
apiCall.query({
type: 'engagement',
engagement_status__in: '0,1'
}, function(data) {
var engagements = {};
$.each(data.objects, function (i, engagement) {
engagements[engagement.lawyer_id] = engagement
});
$scope.engagements = engagements;
});
});
services.js
angular.module('App.Services', ['ngResource']).
factory('apiCall', function ($resource) {
return $resource('/api/v1/:type',
{type: '#type'},
{
query: {
method: 'GET',
isArray: false
}
}
);
});
controllerSpec.js
describe('controllers', function () {
beforeEach(
module('App', ['App.Controllers', 'App.Directives', 'App.Services'])
);
describe('MarketplaceCtrl', function () {
var scope, ctrl, $httpBackend;
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/api/v1/engagement?engagement_status__in=0,1').
respond([]);
scope = $rootScope.$new();
/* Why is MarketplaceCtrl not working? :( */
ctrl = $controller('MarketplaceCtrl', {$scope: scope});
}));
it('should have a MarketplaceCtrl controller', (function () {
expect(ctrl).not.to.equal(null);
}));
});
});
Ended up using this example https://github.com/tebriel/angular-seed/commit/b653ce8e642ebd3e2978d5404db81897edc88bcb#commitcomment-3416223
Basically:
describe('controllers', function(){
beforeEach(module('myApp.controllers'));
it('should ....', inject(function($controller) {
//spec body
var myCtrl1 = $controller('MyCtrl1');
expect(myCtrl1).toBeDefined();
}));
it('should ....', inject(function($controller) {
//spec body
var myCtrl2 = $controller('MyCtrl2');
expect(myCtrl2).toBeDefined();
}));
});