首页 文章

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

提问于
浏览
143

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

angular.module('app.myService', ['app.myOtherService'])
  .factory('myService', [myOtherService,

    function(myOtherService) {
      function makeRemoteCall() {
        return myOtherService.makeRemoteCallReturningPromise();
      }

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

要对 myService 进行单元测试,我需要模拟 myOtherService ,这样它的 makeRemoteCallReturningPromise() 方法返回一个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');
      });    
  }));

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

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

8 回答

  • 0

    您可以使用像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);
    
    
        });
    
  • 69

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

    spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
    

    对于Jasmine 2:

    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
    

    (复制自评论,感谢ccnokes)

  • 0

    使用 sinon

    const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
                         .returns(httpPromise(200));
    

    众所周知, httpPromise 可以是:

    const httpPromise = (code) => new Promise((resolve, reject) =>
      (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
    );
    
  • 8
    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();  
        });
    });
    
  • 12

    老实说..你依靠注入来模拟服务而不是模块,这是错误的做法 . 另外,在beforeEach中调用inject是一种反模式,因为它使得模拟在每个测试的基础上变得困难 .

    以下是我将如何做到这一点......

    module(function ($provide) {
      // By using a decorator we can access $q and stub our method with a promise.
      $provide.decorator('myOtherService', function ($delegate, $q) {
    
        $delegate.makeRemoteCallReturningPromise = function () {
          var dfd = $q.defer();
          dfd.resolve('some value');
          return dfd.promise;
        };
      });
    });
    

    现在,当您注入服务时,它将具有正确模拟的使用方法 .

  • 170

    我发现有用的,刺穿服务函数为sinon.stub() . returns($ q.when({})):

    this.myService = {
       myFunction: sinon.stub().returns( $q.when( {} ) )
    };
    
    this.scope = $rootScope.$new();
    this.angularStubs = {
        myService: this.myService,
        $scope: this.scope
    };
    this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );
    

    控制器:

    this.someMethod = function(someObj) {
       myService.myFunction( someObj ).then( function() {
            someObj.loaded = 'bla-bla';
       }, function() {
            // failure
       } );   
    };
    

    并测试

    const obj = {
        field: 'value'
    };
    this.ctrl.someMethod( obj );
    
    this.scope.$digest();
    
    expect( this.myService.myFunction ).toHaveBeenCalled();
    expect( obj.loaded ).toEqual( 'bla-bla' );
    
  • 2

    代码段:

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

    可以用更简洁的形式编写:

    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
        return $q.resolve('Remote call result');
    });
    
  • 0

    我'm not sure why the way you did it doesn'工作,但我通常使用 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');
          });    
      }));
    

    还要记住,您需要调用 $digest 来调用 then 函数 . 请参阅$q documentationTesting 部分 .

    ------EDIT------

    仔细看看你正在做什么之后,我想我在你的代码中看到了问题 . 在 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;   
        };
      }
    

相关问题