如何在Angularjs Jasmine单元测试中模拟返回承诺的服务?

Geo*_*kov 149 javascript unit-testing mocking jasmine angularjs

我有myService使用myOtherService,它进行远程调用,返回promise:

angular.module('app.myService', ['app.myOtherService'])
  .factory('myService', [
    myOtherService,
    function(myOtherService) {
      function makeRemoteCall() {
        return myOtherService.makeRemoteCallReturningPromise();
      }

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])
Run Code Online (Sandbox Code Playgroud)

为了进行单元测试myService我需要模拟myOtherService,这样它的myService方法返回一个promise.我是这样做的:

describe('Testing remote call returning promise', function() {
  var myService;
  var myOtherServiceMock = {};

  beforeEach(module('app.myService'));

  // I have to inject mock when calling module(),
  // and module() should come before any inject()
  beforeEach(module(function ($provide) {
    $provide.value('myOtherService', myOtherServiceMock);
  }));

  // However, in order to properly construct my mock
  // I need $q, which can give me a promise
  beforeEach(inject(function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock = {
      makeRemoteCallReturningPromise: function() {
        var deferred = $q.defer();

        deferred.resolve('Remote call result');

        return deferred.promise;
      }    
    };
  }

  // Here the value of myOtherServiceMock is not
  // updated, and it is still {}
  it('can do remote call', inject(function() {
    myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
      .then(function() {
        console.log('Success');
      });    
  }));  
Run Code Online (Sandbox Code Playgroud)

从上面可以看出,我的模拟的定义取决于myOtherService我必须加载使用makeRemoteCallReturningPromise.此外,注入模拟应该发生$q,应该在之前 inject().但是,一旦我更改了mock的值,它就不会更新.

这样做的正确方法是什么?

dnc*_*253 175

我不确定为什么你做它的方式不起作用,但我通常用这个spyOn功能来做.像这样的东西:

describe('Testing remote call returning promise', function() {
  var myService;

  beforeEach(module('app.myService'));

  beforeEach(inject( function(_myService_, myOtherService, $q){
    myService = _myService_;
    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;
    });
  }

  it('can do remote call', inject(function() {
    myService.makeRemoteCall()
      .then(function() {
        console.log('Success');
      });    
  }));
Run Code Online (Sandbox Code Playgroud)

还要记住,您需要$digest调用要调用的then函数.请参阅$ q文档的" 测试"部分.

- - - 编辑 - - -

仔细看看你正在做什么之后,我想我在你的代码中看到了问题.在beforeEach,你正在设置myOtherServiceMock一个全新的对象.将$provide永远不会看到这个参考.您只需要更新现有的参考:

beforeEach(inject( function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock.makeRemoteCallReturningPromise = function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;   
    };
  }
Run Code Online (Sandbox Code Playgroud)

  • @JimAho通常你只需注入`$ rootScope`并在其上调用`$ digest`. (7认同)

Pri*_*ngh 69

我们也可以直接通过间谍编写茉莉花实现的回复承诺.

spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
Run Code Online (Sandbox Code Playgroud)

对于Jasmine 2:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
Run Code Online (Sandbox Code Playgroud)

(复制自评论,感谢ccnokes)

  • 注意使用Jasmine 2.0的人,.andReturn()已被.and.returnValue取代.所以上面的例子是:`spyOn(myOtherService,"makeRemoteCallReturningPromise").和.returnValue($ q.when({}));`我刚刚杀了半个小时搞清楚了. (12认同)

Dar*_*ett 13

describe('testing a method() on a service', function () {    

    var mock, service

    function init(){
         return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
                mock = $injector.get('service_that_is_being_mocked');;                    
                service = __serviceUnderTest_;
            });
    }

    beforeEach(module('yourApp'));
    beforeEach(init());

    it('that has a then', function () {
       //arrange                   
        var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
            return {
                then: function (callback) {
                    return callback({'foo' : "bar"});
                }
            };
        });

        //act                
        var result = service.actionUnderTest(); // does cleverness

        //assert 
        expect(spy).toHaveBeenCalled();  
    });
});
Run Code Online (Sandbox Code Playgroud)


Mik*_*unn 8

您可以使用像sinon这样的存根库来模拟您的服务.然后您可以返回$ q.when()作为您的承诺.如果scope对象的值来自promise结果,则需要调用scope.$ root.$ digest().

var scope, controller, datacontextMock, customer;
  beforeEach(function () {
        module('app');
        inject(function ($rootScope, $controller,common, datacontext) {
            scope = $rootScope.$new();
            var $q = common.$q;
            datacontextMock = sinon.stub(datacontext);
            customer = {id:1};
           datacontextMock.customer.returns($q.when(customer));

            controller = $controller('Index', { $scope: scope });

        })
    });


    it('customer id to be 1.', function () {


            scope.$root.$digest();
            expect(controller.customer.id).toBe(1);


    });
Run Code Online (Sandbox Code Playgroud)

  • 这是缺少的部分,调用`$ rootScope。$ digest()`以得到要解决的承诺 (2认同)