I have a controller with one function exposed in the $scope that calls a service, loginService
$scope.validateCredentials = function (callback){
loginService.validate($scope.username, $scope.password)
.then(function (){
self.credentialsRight();
if (typeof callback !== "undefined") {
callback("credentials right callback");
}
}, function (){
self.credentialsWrong();
if (typeof callback !== "undefined") {
callback("credentials wrong callback");
}
})
}
I have managed to test if the beforementioned method works right testing it with jasmine like this
it("validateCredentials - passing in the right credentials", function() {
spyOn(loginService, 'validate').andCallFake(function() {
return successfulDeferred.promise;
});
$scope.validateCredentials(function() {
expect($scope.error).toBe(false);
});
$scope.$apply();
});
The reason why I call andCallFake using an spy is because I want to fake a promise being returned. Please note I have tried to do this using $httpBackend.onPOST unsusccefully.
However, I feel that the usage of a callback in my controller only for testing purposes and deal with an async response is weird. Do you guys know a better way to implement it? I have seen waitsFor and runs but doesn't seem to work for me.
Here is a similar test that I have in my code base. You will need to adapt for your solution. But basically you need to call deferred.reject(someData) after you call the service method. Then you may need to call a digest on the root scope. Then test your expectation. Replace personApi with your loginService.
//setup the controller
var $scope, ctrl, deferred, personApi, rootScope, log,basePerson;
beforeEach(inject(function ($rootScope, $q, $controller, _personApi_) {
rootScope = $rootScope;
$scope = $rootScope.$new();
deferred = $q.defer();
personApi = _personApi_;
ctrl = $controller('personCtrl', {
$scope: $scope,
personApi: personApi
});
}));
it('should map returned errors to the original object', function () {
spyOn(personApi, 'save').and.callFake(function () {
return deferred.promise;
});
$scope.person = basePerson;
deferred.reject(stronglyNamedErrors);
$scope.savePerson();
$scope.$root.$digest();
expect($scope.person.firstNameError).toBe(stronglyNamedErrors.firstNameError);
});
Related
I'm new to jasmine testing. How can I test function call in watch function?
Below is my code. I'm confused about usage of spy in jasmine and how can I handle function call inside watcher.
Do I need to pause fetch() inside watch. Please suggest how to improve my testing skills.
var app = angular.module('instantsearch',[]);
app.controller('instantSearchCtrl',function($scope,$http,$sce){
$scope.$sce=$sce;
$scope.$watch('search', function() {
fetch();
});
$scope.search = "How create an array";
var result = {};
function fetch() {
$http.get("https://api.stackexchange.com/2.2/search?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!4*Zo7ZC5C2H6BJxWq&key=DIoPmtUvEkXKjWdZB*d1nw((")
.then(function(response) {
$scope.items = response.data.items;
$scope.answers={};
angular.forEach($scope.items, function(value, key) {
var ques = value.question_id;
$http.get("https://api.stackexchange.com/2.2/questions/"+value.question_id+"/answers?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!9YdnSMKKT&key=DIoPmtUvEkXKjWdZB*d1nw((").then(function(response2) {
$scope.answers[ques]=response2.data.items;
//console.log(JSON.stringify($scope.answers));
});
});
});
}
});
my test case:
describe('instantSearchCtrl', function() {
beforeEach(module('instantsearch'));
var $scope, ctrl;
beforeEach( inject(function($rootScope, $controller) {
// create a scope object for us to use.
$scope = $rootScope.$new();
ctrl = $controller('instantSearchCtrl', {
$scope: $scope
});
}));
/*var $scope = {};
var controller = $controller('instantSearchCtrl', { $scope: $scope });
expect($scope.search).toEqual('How create an array');
//expect($scope.strength).toEqual('strong');*/
it('should update baz when bar is changed', function (){
//$apply the change to trigger the $watch.
$scope.$apply();
//fetch().toHaveBeenCalled();
fetch();
it(" http ", function(){
//scope = $rootScope.$new();
var httpBackend;
httpBackend = $httpBackend;
httpBackend.when("GET", "https://api.stackexchange.com/2.2/search?page=1&pagesize=10&order=desc&sort=activity&intitle="+$scope.search+"&site=stackoverflow&filter=!4*Zo7ZC5C2H6BJxWq&key=DIoPmtUvEkXKjWdZB*d1nw((").respond([{}, {}, {}]);
});
});
});
First you should trigger the watch. For that you should change search value and after that manually run: $scope.$digest() or $scope.$apply()
In order to fully test the fetch function you should also mock the response to the second request (or the mock for all the second level requests if you want to test the iteration).
After that you should add some expect statements. For the controller code they should be:
expect($scope.items).toEqual(mockResponseToFirstRequest);
expect($scope.answers).toEqual(mockResponseCombinedForSecondLevelRequests);
As for using the spy in karma-jasmine tests, those limit the amount of the code tested. A plausible use for spy in this case is to replace the httpBackend.when with spyOn($http, 'get').and.callFake(function () {})
Here is the documentation for using spies https://jasmine.github.io/2.0/introduction.html#section-Spies
I'm trying to test function calls from my controller's init function. I route various logic based on stateParams and want to write unit tests against this scenarios but I'm having trouble getting it working.
My init function
var _init = function () {
// Get full companyList
servicecall().then(function (response) {
if ($stateParams.ID) {
$scope.callOne();
}
else if ($stateParams.Index == -1) {
$scope.callTwo();
}
else if ($stateParams.Index == '' || $stateParams.Index == null) {
$scope.callThree();
}
else
$scope.callFour();
},
function (err) {
console.log(err);
});
};
_init();
So simply I want to set $stateParams.Index = -1, and make sure callTwo() gets called.
My beforeEach looks like
beforeEach(function () {
controller = $controller('Controller', {
$scope: $scope,
$stateParams: $stateParams,
$state: $state
});
// And add $scope's spyOns
spyOn($scope, 'callOne').and.callThrough();
spyOn($scope, 'callTwo').and.callThrough();
spyOn($scope, 'callThree').and.callThrough();
spyOn($scope, 'callFour').and.callThrough();
});
At first I tried the below, which of course did not work; it says spy was not called.
it("should call callTwo if stateParams.index is -1", function () {
$stateParams.Index = -1;
expect($scope.callTwo()).toHaveBeenCalled();
});
I figured that all of the init was happening before my spy attached, so then I tried moving that stuff inside of my it call but everything broke down.
This one says callTwo has already been spied upon.
it("should call callTwo if stateParams is -1", function () {
$stateParams.Index = -1;
spyOn($scope, 'callTwo').and.callThrough();
controller = $controller('Controller', {
$scope: $scope,
$stateParams: $stateParams,
$state: $state
});
expect($scope.callTwo).toHaveBeenCalled();
});
But if I move the spy declaration after the controller is initialized it says it's not called again
How can I ensure calls are being made as expected during a controller instantiation?
Not sure if it's the best solution, but currently nothing else comes to mind and this definitely works.
You can have a describe for every such test, since the $stateParams are injected when you create your controller. That's why doing it inside the it is not sufficient since the $scope at that time belongs to the controller already created.
So you need to have:
describe('Controller tests for when Index === -1', function () {
beforeEach(function (...) {
$stateParams.Index = -1;
controller = $controller('Controller', {
$scope: $scope,
$stateParams: $stateParams,
$state: $state
});
}
// it tests for Index === -1 case
});
So in this example all of the it tests you'll have are guaranteed to have $stateParams === 1. Same will go for your other values.
I did this controller
app.controller('controller',['$scope','httpServices',function($scope,httpServices){
$scope.items= undefined;
httpServices.getItems( function(items){
$scope.items= items;
});
}]);
and I wrote this test
describe('controller', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('controller', {
'$scope': scope
});
}));
it('defined', function () {
expect(scope.items).toBeUndefined();
})
});
How I can test the scope.items after to have called the service?
I assume that your service httpServices is making some http requests. Therefore you should use the mock-backend service in order to test your controller.
Something like this, pay attention to the comments that I've made inside the code:
describe('Your specs', function() {
var $scope,
$controller,
$httpBackend;
// Load the services's module
beforeEach(module('yourApp'));
beforeEach(inject(function(_$controller_, $rootScope, _$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$controller = _$controller_;
//THIS LINE IS VERY IMPORTANT, HERE YOU HAVE TO MOCK THE RESPONSE FROM THE BACKEND
$httpBackend.when('GET', 'http://WHATEVER.COM/API/SOMETHING/').respond({});
var createController = function(){
$controller('controller', {$scope: $scope});
}
}));
describe('Your controller', function() {
it('items should be undefined', function() {
createController();
expect(scope.items).toBeUndefined();
});
it('items should exist after getting the response from the server', function () {
//THIS LINE IS ALSO VERY IMPORTANT, IT EMULATES THE RESPONSE FROM THE SERVER
$httpBackend.flush();
expect(scope.items).toBeDefined();
});
});
});
The question title states this is to test a service, but the code of the question looks like an attempt is being made to test the controller. This answer describes how to test the controller.
If you're testing the controller that calls httpServices.getItems, then you need to mock it/stub getItems in order to
Control it on the test
Not assume any behaviour of the real httpServices.getItems. After all, you're testing the controller, and not the service.
A way to do this is in a beforeEach block (called before the controller is created) provide a fake implementation of getItems that just saves the callback passed to it.
var callback;
beforeEach(inject(function(httpServices) {
callback = null;
spyOn(httpServices, 'getItems').and.callFake(function(_callback_) {
callback = _callback_;
});
});
In the test you can then call this callback, passing in some fake data, and test that this has been set properly on the scope.
it('saves the items passed to the callback on the scope', function () {
var testItems = {};
callback(testItems);
expect($scope.items).toBe(testItems);
});
This can be seen working at http://plnkr.co/edit/Z7N6pZjCS9ojs9PZFD04?p=preview
If you do want to test httpServices.getItems itself, then separate tests are the place for that. Assuming getItems calls $http, then you are most likely to need to use $httpBackend to handle mock responses. Most likely, these tests would not instantiate any controller, and I suspect not need to do anything on any scope.
I have the following test for a service object and the promise doesn't return and neither does the http request get called from inside the service, but it works in browser testing.
'use strict';
describe('Service: AuthService', function () {
// load the controller's module
beforeEach(module('adminPanelAngularApp'));
var AuthService, AuthService, $rootScope;
// Initialize the controller and a mock scope
beforeEach(inject(function (_AuthService_, _$rootScope_) {
AuthService = _AuthService_;
$rootScope = _$rootScope_;
}));
it('it auths', function () {
AuthService.login(SOMECREDENTIALS).then(function(){
console.log('this doesnt output in log');
});
expect(3).toBe(3);
});
});
this is my service
angular.module('adminPanelAngularApp').factory('AuthService', ['$http', '$cookieStore', '$rootScope', '$location', '$q', function ($http, $cookieStore, $rootScope, $location, $q) {
var authService = {};
....
authService.get_current_user = function(){
return $rootScope.current_user;
}
authService.login = function (credentials) {
var url = REDACTED;
return $http.post(server+url).then(function (res) {
if (!res.data){
return false;
}
if (res.data.error){
$rootScope.login_error = res.data.error;
}
var user = {
email: res.data.email,
session: res.data.session,
uid: res.data.uid
}
$cookieStore.put('loginData', user);
$rootScope.current_user = user;
return user;
});
};
...
what am I doing wrong with the tests?
I know my code is pretty bad too, but if I can test this then i'm halfway there.
If you don't want to mock $http, I suggest you to use $httpBackend.
With $httpBackend you can mock the calls you make with $http.
Imagine this service:
app.factory('Auth', function($http) {
return {
login: function() {
return $http.post('/login');
}
};
});
The goal is to test that you make your $http.post and it returns successfully, so the idea is like:
describe('Service: Auth', function() {
var Auth, $httpBackend;
beforeEach(function() {
module('app');
inject(function(_Auth_, _$httpBackend_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$httpBackend.whenPOST('/login').respond(200);
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should do a proper login', function() {
var foo;
Auth.login().then(function() {
foo = "success";
});
$httpBackend.flush();
expect(foo).toBe("success");
});
});
So, for starters, we inject what we need (Auth and $httpBackend)
And then, we call the whenPOST of $httpBackend. Basically it does something like:
When someone does a POST to /login, respond it with 200
Then on the test, we call login which is going to do the $http.post. To process this $http.post, since it is async, we can simulate the real call doing a $httpBackend.flush() which is going to "process" the call.
After that, we can verify that the .then was executed.
What about the afterEach? We don't really need it for this example, but when you want to assert yes or yes that a call was made, you can change the whenPOST to expectPOST, to make a test fail if that POST is never made. The afterEach is basically checking the status of the $httpBackend to see if any of those expectation weren't matched.
On the other hand, you don't need to create a promise by hand. $http returns a promise for you, so you can return the $http call directly, and on the $http then you can:
return user;
That will simplify the implementation a little bit.
Demo here
I'm a little new at Angular \ SinonJS so please forgive the silly question and bear with if this is obvious. I've done some googling and can't seem to find an answer. I've used SinonJs to do mocking as that was recommended in a Pluralsight video. Unsure if its the best choice. Any alternatives welcome.
I want to test the behaviour of my AngularJS controller and test that it calls my repository Search method with the criteria I specify only once.
I have the following in my controller and am getting the error in my Jasmin test runner:
goal-controller.js:
stepByStepApp.controller("goalController", function ($scope, goalRepository) {
$scope.viewGoalButtonDisabled = true;
$scope.search = function (criteria) {
$scope.errors = [];
return goalRepository.search(criteria).$promise.then(
function (goals) {
$scope.viewGoalButtonDisabled = true;
return goals;
},
function (response) {
$scope.viewGoalButtonDisabled = true;
$scope.errors = response.data;
});
};
});
goal-controller-tests.js
'use strict';
(function () {
describe('Given a Goal Controller', function () {
var scope, controller, goalRepositoryMock, goals, criteria;
beforeEach(function () {
module('stepByStepApp');
inject(function ($rootScope, $controller, goalRepository) {
scope = $rootScope.$new();
goalRepositoryMock = sinon.mock(goalRepository);
goals = [{ foo: 'bar' }];
criteria = 'test search criteria';
controller = $controller('goalController', { $scope: scope });
});
});
it('the View Goal Button should be disabled', function () {
expect(scope.viewGoalButtonDisabled).toBe(true);
});
describe("when a goal is searched for, it", function () {
it("should search the Goal Repository", function () {
goalRepositoryMock.expects('search').once().returns(goals);
scope.search(criteria);
goalRepositoryMock.verify();
});
});
});
}())
I am getting the following error:
2 specs, 1 failure
Given a Goal Controller
when a goal is searched for, it
should search the Goal Repository
TypeError: Cannot read property 'then' of undefined
I'm clearly not mocking the call to "goalRepository.search(criteria).$promise.then" properly. How do I mock the $promise and .then properly? Thanks in advance.
I'm assuming that this repository is returning a resource object. With that said, here is how I would go about testing this controller.
Here is the working plunk.
Initial beforeEach block
You need to mock out the promise chain. For that, you need to inject the $q service. Below is how my class wide beforeEach statement. I use stubs for mocking. I inject that mock into my controller under test.
beforeEach(inject(function($controller, $rootScope, $q) {
q = $q
scope = $rootScope;
goalRepositoryStub = sinon.stub({
search: function() {}
});
testCtrl = $controller("goalController", {
$scope: scope,
goalRepository: goalRepositoryStub
});
}));
With that mocked repository I now have complete control over what it does.
beforeEach for testing repo
In this block I actually mock out the entire promise chain. I get a defer object from the q service. From it I get a promise. I then put that promise in a fake resource object. And I then return that fake resource object whenever search is called. I then call the search on scope.
beforeEach(function() {
deferred = q.defer();
promise = deferred.promise;
returnedResource = {
$promise: promise
};
goalRepositoryStub.search.returns(returnedResource);
scope.search(criteria);
});
The Actual Testing
For the actual testing you need to tell that deferred object what to do (either reject or resolve the promise) and trigger the scopes $apply() function. You then test to see if your code is doing what it should be doing.
Here is an example of how I would test a successful call to goalRepository:
describe('successful goalRepository call', function() {
beforeEach(function() {
deferred.resolve(dataToReturn);
scope.$apply();
});
it('should add the data to scope.goals.', function() {
expect(scope.goals).toBe(dataToReturn);
});
it('should not change scope.failureApi to true.', function() {
expect(scope.viewGoalButtonDisabled).toBeFalsy();
});
});
These aren't necessary "best practices" or anything. Just a way that I found to solve this particular problem on my own.