如何在Jasmine中模拟Angular服务的功能

时间:2012-11-16 17:52:40

标签: unit-testing mocking angularjs jasmine

我有以下角度控制器

function IndexCtrl($scope, $http, $cookies) {   

    //get list of resources
    $http.get(wtm.apiServer + '/v1/developers/me?access_token=' + $cookies['wtmdevsid']).
    success(function(data, status, headers, config) {
        // snip 
      }).
      error(function(data, status, headers, config) {
        // snip
      });

$scope.modal = function() {
      // snip
}

return;
}

我要做的是在$ http服务上模拟get方法。这是我的单元测试代码:

describe('A first test suite', function(){
    it("A trivial test", function() {
         expect(true).toBe(true);
    });
});

describe('Apps', function(){
describe('IndexCtrl', function(){
    var scope, ctrl, $httpBackend;
    var scope, http, cookies = {wtmdevsid:0};

    beforeEach(inject(function($injector, $rootScope, $controller, $http) {
        scope = $rootScope.$new();

         ctrl = new $controller('IndexCtrl', {$scope: scope, $http: $http, $cookies: cookies});
         spyOn($http, 'get');
         spyOn(scope, 'modal');

    }));

    it('should create IndexCtrl', function() {
        var quux = scope.modal();
        expect(scope.modal).toHaveBeenCalled();
        expect($http.get).toHaveBeenCalled();
    });
  });
});

当我跑步时,我得到了     ReferenceError:未定义wtm。

wtm是一个全局对象,当然我在运行测试时不会定义它,因为我运行测试时没有运行声明它的代码。我想知道的是为什么要调用真正的$ http.get函数,以及如何设置间谍或存根以便我实际上不调用真正的函数?

(inb4讨厌全局变量:我的一个同事的任务是将我们的代码中的因素分解:) :)

3 个答案:

答案 0 :(得分:4)

您需要在测试之前连接whenGET $httpBackend方法。尝试在“使用模拟后端进行单元测试”下的测试的beforeEach()函数中进行设置... There is a good example here

答案 1 :(得分:4)

我建议所有使用此处所述方式的全局变量应通过$window服务使用。

所有可用的全局变量,例如window.wtm,也将在$ window.atm上提供。

然后你可以完全删除你的wtm引用并以你已经描述的方式监视它:

 var element, $window, $rootScope, $compile;

  beforeEach(function() {
    module('fooApp', function($provide) {
        $provide.decorator('$window', function($delegate) {

            $delegate.wtm = jasmine.createSpy();

            return $delegate;
        });
    });

    inject(function(_$rootScope_, _$compile_, _$window_) {
        $window = _$window_;
        $rootScope = _$rootScope_;
        $compile = _$compile_;
    });     

  });

答案 2 :(得分:1)

也许你可以围绕$ httpBackend创建一个自定义包装器模拟来处理你的特殊需求。

详细说明,Angular会使用最后一次服务策略覆盖同名的组件,这意味着您加载模块的顺序在测试中非常重要。

当您定义另一个具有相同名称的服务并在第一个服务之后加载它时,将注入最后一个服务而不是第一个服务。 E.g:

apptasticMock.service("socket", function($rootScope){
  this.events = {};

  // Receive Events
  this.on = function(eventName, callback){
    if(!this.events[eventName]) this.events[eventName] = [];
    this.events[eventName].push(callback);
  }

  // Send Events
  this.emit = function(eventName, data, emitCallback){
    if(this.events[eventName]){
      angular.forEach(this.events[eventName], function(callback){
        $rootScope.$apply(function() {
          callback(data);
        });
      });
    };
    if(emitCallback) emitCallback();
  }

});

此服务提供完全相同的界面,其行为与原始界面完全相同,但它从不通过任何套接字进行通信。这是我们想要用于测试的服务。

考虑到角度的加载顺序,测试看起来像这样:

describe("Socket Service", function(){
  var socket;

  beforeEach(function(){
    module('apptastic');
    module('apptasticMock');

    inject(function($injector) {
      socket = $injector.get('socket');
    });
  });

  it("emits and receives messages", function(){
    var testReceived = false;

    socket.on("test", function(data){
      testReceived = true;
    });

    socket.emit("test", { info: "test" });
    expect(testReceived).toBe(true);
  });

});

重要的是module('apptasticMock')module('apptastic')之后执行。这会使用模拟的套接字覆盖原始套接字实现。其余的只是正常的依赖注入程序。

This article I wrote对你来说很有意思,因为它会进一步详细说明。