我如何模拟一个在Angularjs Jasmineunit testing中返回承诺的服务?
我有我的服务,使用myOtherService,这使远程调用,返回承诺:
angular.module('app.myService', ['app.myOtherService']) .factory('myService', [myOtherService, function(myOtherService) { function makeRemoteCall() { return myOtherService.makeRemoteCallReturningPromise(); } return { makeRemoteCall: makeRemoteCall }; } ])
要为myService
进行unit testing,我需要模拟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'); }); }));
正如你从上面看到的,我的模拟的定义取决于$q
,我必须使用inject()
加载。 此外,注入模拟应该发生在module()
,它应该在inject()
之前。 但是,一旦我改变它,模拟的值不会更新。
什么是正确的方法来做到这一点?
我不知道为什么你这样做是行不通的,但我通常用spyOn
函数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
调用的函数被调用。 请参阅$ q文档的testing部分。
– – – 编辑 – – –
仔细看看你在做什么,我想我在你的代码中看到了问题。 在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; }; }
我们也可以直接由间谍写茉莉花的回执。
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
对于茉莉花2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(抄评,感谢ccnokes)
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(); }); });
你可以使用像sinon这样的存根库来嘲笑你的服务。 然后你可以返回$ q.when()作为你的承诺。 如果您的范围对象的值来自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); });
老实说,你正在靠这个错误的方式来依靠注入来模拟一个服务而不是模块。 另外,在beforeEach中调用注入是一种反模式,因为它使每个testing都很困难。
这是我怎么做这个…
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; }; }); });
现在,当你注入你的服务时,会有一个适当的模拟方法来使用。
用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 }) );
我发现sinon.stub()返回($ 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 } ); };
并testing
const obj = { field: 'value' }; this.ctrl.someMethod( obj ); this.scope.$digest(); expect( this.myService.myFunction ).toHaveBeenCalled(); expect( obj.loaded ).toEqual( 'bla-bla' );
代码片段:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() { var deferred = $q.defer(); deferred.resolve('Remote call result'); return deferred.promise; });
可以写成更简洁的forms:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() { return $q.resolve('Remote call result'); });