9

I'm working with AngularJS + Karma. configService manages the settings of my app (e.g. the background-color, wether it's on debug mode, general permissions...). It loads initial data with $http. I wrote the test successfully for the service but my directives and controllers use it.

When I write the unit tests for directives, I have to mock the service.

I know I can do:

spyOn(configService, 'getBackgroundColor').andCallFake(function (params) {
   return "red";
});

but the service has 25+ methods and initial data load. I don't feel like writing (and maintaining) this spyOn thing in every test suite. What's more, I load data in the factory with $http, and that should be mocked as well. if I just inject the service and mock the calls, I'll still make the http get request.

What do you think would be the best way to reuse a mock?

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
Eduard Gamonal
  • 8,023
  • 5
  • 41
  • 46
  • Do you just want to share a mock of the service among many tests? Or are you looking for a way to mock the $http call and use the service in your directive and controller tests? – Karen Zilles May 16 '13 at 00:13
  • share the mock of the service among many test suites. more specifically, avoid copy-pasting a long beforeEach(). Yesterday I created a service in a module `myapp.mocks` and included it in testacular.conf.js, and then I load it with `module(myapp.mocks)`. I also extracted the response of my server to another file and created a `var RESPONSE_SERVER = {json here}`. if that sounds like a good solution I'll elaborate more and answer myself below – Eduard Gamonal May 16 '13 at 07:14
  • 1
    That's pretty much what I was going to recommend. I created a .js file that just contained a plain old javascript function that I called to create the mock. Another plain old javascript function to configure the server responses for tests. And like you say, if you define a global variable with the json responses then you can use that in your tests to compare against. – Karen Zilles May 17 '13 at 21:43
  • Answered http://stackoverflow.com/questions/19680105/how-do-i-neatly-provide-re-usable-sample-data-values-to-my-angularjs-jasmine-u/19682501#19680105 using this as an example – Eduard Gamonal Oct 31 '13 at 16:30

2 Answers2

9

Instead of spraying the testing code with spies you can create a proper mock of the service in its own module and add it in any test that needs it.

The controller unit test sits in a test/spec/modules/user/controller.js file.

The mocked service sits in a test/mock/modules/user/service.js file.

For a controller method:

$scope.refreshList = function() {
  UserService.all(pageNumber, size, sort, function(data) {
    $scope.users = data.content;
    $scope.page = data.page;
  });
};

the mocked service:

(function () {

'use strict';

angular.module('app.user.mock', ['app.user']);

angular.module('app.user.mock').factory('UserServiceMock',
  ['$q',
  function($q) {
    var factory = {};

    factory.mockedUsers = { 
      content: [ { firstname: 'Spirou', lastname: 'Fantasio', email: 'spirou@yahoo.se', workPhone: '983743464365' } ],
      page: '1'
    };

    factory.search = function(searchTerm, page, size, sort, callback) {
      var defer = $q.defer();
      defer.resolve(this.mockedUsers);
      defer.promise.then(callback);
      return defer.promise;
    };

    factory.all = function(page, size, sort, callback) {
      var defer = $q.defer();
      defer.resolve(this.mockedUsers);
      defer.promise.then(callback);
      return defer.promise;
    };

    return factory;
  }
]);

})();

and the controller unit test:

(function () {

'use strict';

var $scope;
var listController;
var UserServiceMock;

beforeEach(function() {
  module('app.project');
  module('app.user.mock'); // (1)
});

beforeEach(inject(function($rootScope, _UserServiceMock_) {
  $scope = $rootScope.$new();
  UserServiceMock = _UserServiceMock_; // (2)
}));

describe('user.listCtrl', function() {

  beforeEach(inject(function($controller) {
    listController = $controller('user.listCtrl', {
      $scope: $scope,
      UserService: UserServiceMock
    });
  }));

  it('should have a search function', function () { // (3)
    expect(angular.isFunction(UserServiceMock.search)).toBe(true);
  });

  it('should have an all function', function () {
    expect(angular.isFunction(UserServiceMock.all)).toBe(true);
  });

  it('should have mocked users in the service', function () {
    expect(UserServiceMock.mockedUsers).toBeDefined();
  });

  it('should set the list of users in the scope', function (){
    expect($scope.users).not.toEqual(UserServiceMock.mockedUsers);
    $scope.refreshList();
    $scope.$digest();
    expect($scope.users).toEqual(UserServiceMock.mockedUsers.content);
  });

});

})();

You add the app.user.mock module containing the mocked service (1) and inject the mocked service in the controller (2).

You can then test your mocked service has been injected (3).

Stephane
  • 11,836
  • 25
  • 112
  • 175
  • Do you know of any way to get jasmine into a mock like this? So that I can use `jasmine.spyOn` instead of tracking calls by hand. – dshepherd Jun 08 '16 at 09:20
0

I created a .js file that just contained a plain old javascript function that I called to create the mock. Another plain old javascript function to configure the server responses for tests. And like you say, if you define a global variable with the json responses then you can use that in your tests to compare against

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265