3

我有以下角度控制器

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 is not defined。

wtm is a global object and of course it wouldn't be defined when I run my test because the code that it is declared in is not run when I run my test. What I want to know is why the real $http.get function is being called and how do I set up a spy or a stub so that I don't actually call the real function?

(inb4 hating on globals: one of my coworkers has been tasked with factoring those out of our code :) )

4

3 回答 3

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

  });
于 2013-04-07T18:55:50.017 回答
4

您需要在测试之前连接您的whenGET方法$httpBackendbeforeEach()尝试在您的测试功能中设置它......在“使用模拟后端的单元测试”下有一个很好的例子。

于 2012-11-16T18:20:24.893 回答
1

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

详细地说,Angular 使用后到先服务的策略覆盖同名组件,这意味着加载模块的顺序在测试中很重要。

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

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'). 这会用模拟的套接字实现覆盖原始套接字实现。其余的只是正常的依赖注入过程。

我写的这篇文章对你来说可能会很有趣,因为它涉及到更多的细节。

于 2013-03-24T16:12:54.877 回答