Mocking an Angular Repository with SinonJs - angularjs

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.

Related

Angular ui-route testing using mocha chai and sinon

I need to test below code in angularjs using mocha chai and sinon
$scope.send = function() {
$state.transitionTo('module.sendhome');
};
Below is test case for the same
it('send' , function () {
scope.send();
});
on running the above test case getting error as given below.
Error: No such state 'module.sendhome'
In my test case need to check if $state.transitionTo is called with parameter module.sendhome.
You need to stub out $state and the transitionTo method and write expectations on that. This will keep your unit test clean and flexible, so as to not trigger the real implementation of $state.transitionTo (which in turn triggers the error you are experiencing).
var $scope, $state;
beforeEach(function () {
$state = {};
module('your_module', function ($provide) {
$provide.value('$state', $state);
});
inject(function ($injector, $controller) {
$state = $injector.get('$state');
$scope = $injector.get('$rootScope').$new();
$controller('your_controller', {
$scope: $scope,
$state: $state
});
});
// Stub API
$state.transitionTo = sinon.stub();
});
it('calls the transitionTo method', function () {
$scope.send();
expect($state.transitionTo).to
.have.been.calledOnce
.and.calledWith('module.sendhome');
});
Edit
As per the notion of not stubbing out things we do not own (which, I don't fully agree on but for the sake of argument let's say I do).
Don't stub $state.transitionTo, but rather spy on it.
Now - you will have to register a state matching that of your expectation in order for $state.transitionTo to not crash.
var stateProvider;
beforeEach(function () {
module('ui.router', function ($stateProvider) {
stateProvider = $stateProvider;
});
/** The rest of your beforeEach block **/
stateProvider.state('module.sendhome', {});
});
And then in your it:
it('calls the transitionTo method with the correct params', function () {
var spy = sinon.spy($state, 'transitionTo');
$scope.send();
expect(spy).to
.have.been.calledOnce
.and.calledWith('module.sendhome');
});
Edit#2
If you want to ensure that you ended up on the correct state after invoking your $scope method, I would look into this awesomely awesome stateMock.
Inject stateMock as another module prior to your own and write expectations such as:
afterEach(function () {
$state.ensureAllTransitionsHappened();
});
it('should travel to the correct state', function () {
$state.expectTransitionTo('module.sendhome');
$scope.send();
});

Testing angular controller initialisation with different conditions

I have a controller that takes a dependency on a service, and as part of it's initialisation calls a function on the service. Here's a contrived example:
describe('tests', function() {
var _scope, service, serviceValue = 'value';
beforeEach(module('app'));
beforeEach(inject(['$rootScope','$controller', function($rootScope, $controller) {
_scope = $rootScope.$new();
service = {
get: function(key) {
return serviceValue;
}
};
$controller('myController', {
'$scope': _scope,
'service': service
});
}]));
describe('initialisation', function() {
describe('key exists', function() {
it('should find the key', function() {
expect(_scope.message).toBe('found the key');
});
});
describe('key does not exist', function() {
beforeEach(function() {
serviceValue = undefined;
});
it('should not find the key', function() {
expect(_scope.message).toBe('did not find the key');
});
});
});
});
angular.module('app').controller('myController', ['$scope','service',
function($scope, service) {
if(service.get('key') === 'value') {
$scope.message = 'found the key';
} else {
$scope.message = 'did not find the key';
}
});
The tests for when the key does not exist fail because the controller initialisation has run in the first beforeEach, before the next beforeEach runs to change the service return value.
I can get around this by recreating the whole controller in the beforeEach of the 'key does not exist' tests, but this seems wrong to me, as it initialises the controller twice for the test. Is there a way to get the controller initialisation to run for every test, but after all other beforeEach functions have run.
Is this the right way to be initialising controllers? Am I missing some feature of jasmine?
Creating the controller for each test is the recommended way, especially when you have initialization logic.
I would however use Jasmine's spyOn to set up what the service returns and tracking calls to it, instead of modifying internal values of a mocked or real service.
Inject the real service and save it in a variable, and define a function that creates the controller:
describe('tests', function() {
var $scope, createController, service;
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, _service_) {
$scope = $rootScope.$new();
service = _service_;
createController = function() {
$controller('myController', {
'$scope': $scope,
'service': service
});
};
});
});
For each test use spyOn to intercept calls to the service and decide what it should return, then create the controller:
describe('initialisation', function() {
it('should find the key', function() {
spyOn(service, 'get').and.returnValue('value');
createController();
expect($scope.message).toBe('found the key');
});
it('should not find the key', function() {
spyOn(service, 'get').and.returnValue(undefined);
createController();
expect($scope.message).toBe('did not find the key');
});
});
Demo: http://plnkr.co/edit/BMniTis1RbOR0h5O4kZi?p=preview
As spyOn sets up tracking you can now for example also make sure the service only gets called once on controller initilization:
spyOn(service, 'get').and.returnValue('value');
expect(service.get.calls.count()).toEqual(0);
createController();
expect(service.get.calls.count()).toEqual(1);
Note: The examples above use Jasmine 2.0. Syntaxes will have to be slightly modified for older versions.

AngularJs: Test service with Jasmine

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.

jasmine testing controller calling service from scope function

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);
});

Testing $resource services in AngularJS

I am trying to begin writing unit tests for my angular application and hit a stopping block pretty quick as I am unsure of how exactly to mock my service in a testable way.
Is there a way to mock the REST call otherwise it would seem like I need to mirror everything within my service in my tests which doesn't seem right to me, but I am rather new to test writing so maybe this is how it is supposed to be accomplished. Any help would be greatly appreciated.
My service is as follows:
angular.module('resources.users', ['ngResource'])
.factory('User', function($resource) {
var resource = $resource('/api/index.php/users/:username', {}, {
'update': {method: 'PUT'}
});
resource.getUser = function(username, successCb) {
return resource.query({username: username}, successCb);
};
return resource;
});
My test consists thus far of:
describe('User', function() {
var mockUserResource;
beforeEach(module('resources.users'));
beforeEach(function() {
mockUserResource = sinon.stub({
getUser: function(username) {
mockUserResource.query({username: username});
},
query: function() {}
});
module(function($provide) {
$provide.value('User', mockUserResource);
})
});
describe('getUser', function() {
it('should call getUser with username', inject(function(User) {
User.getUser('test');
expect(mockUserResource.query.args[0][0]).toEqual({username: 'test'});
}));
})
});
You can mock the requests made by ngResource like this:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
Demo
zsong's answer greatly helped me understand this, but I would like to expand on how it works. In case it gets edited, I list the code again here:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
What's going on here?
1
beforeEach(angular.mock.module('myApp'));
We tell the Angular injector ($injector and angular.mock.inject) to inject things defined in the myApp module. You can think of it as defining a module dependency without a dependent module. Compare with how things defined in the myApp module can be injected in, say, a controller in a angular.module('myOtherApp', ['myApp']) module.
2
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
Before each spec, run the function ($injector) function with dependencies injected. In this case, the dependency ($injector) is resolved implicitly from the parameter name. A functionally equivalent variant of this snippet is
beforeEach(function () {
angular.mock.inject(['$httpBackend', 'User', function ($httpB, User) {
$httpBackend = $httpB;
mockUserResource = User;
}]);
});
Here we have instead declared the dependencies explicitly, and are free to use any parameter names we wish.
3
it('should call getUser with username', inject(function (User) {
Again, the test function is injected with the implicitly resolved User service as a parameter, though it isn't actually used.
Notice that this time there is no wrapper function around the inject call. inject invokes the passed function immediately if a spec is currently running, but otherwise it returns a wrapper function (see the inject docs and source code), so we don't actually need the wrapper function. Thus, we could have written the beforeEach snippet above like this:
beforeEach(angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
}));

Resources