Javascript 如何在AngularJS Jasmine单元测试中模拟返回承诺的服务?
我有Javascript 如何在AngularJS Jasmine单元测试中模拟返回承诺的服务?,javascript,angularjs,unit-testing,mocking,jasmine,Javascript,Angularjs,Unit Testing,Mocking,Jasmine,我有myService,它使用myOtherService,进行远程呼叫,返回承诺: angular.module('app.myService', ['app.myOtherService']) .factory('myService', [ myOtherService, function(myOtherService) { function makeRemoteCall() { return myOtherService.makeRemote
myService
,它使用myOtherService
,进行远程呼叫,返回承诺:
angular.module('app.myService', ['app.myOtherService'])
.factory('myService', [
myOtherService,
function(myOtherService) {
function makeRemoteCall() {
return myOtherService.makeRemoteCallReturningPromise();
}
return {
makeRemoteCall: makeRemoteCall
};
}
])
要对myService
进行单元测试,我需要模拟myOtherService
,以便其makeRemoteCallReturningPromise
方法返回承诺。我就是这样做的:
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()
之前。但是,一旦我更改了模拟的值,它就不会更新
做这件事的正确方法是什么?我不知道为什么你的方法不起作用,但我通常使用
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
以调用函数,然后调用函数。请参阅本手册的测试部分
----编辑---
在仔细观察您正在做的事情之后,我想我看到了代码中的问题。在每个之前的中,您将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)您可以使用像sinon这样的存根库来模拟您的服务。然后,您可以返回$q.when()作为您的承诺。如果scope对象的值来自promise结果,则需要调用scope.$root.$digest()
老实说。。通过依赖inject来模拟服务而不是模块,这是错误的。此外,在beforeach中调用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;
};
});
});
现在,当您注入服务时,它将有一个适当的模拟方法供使用。使用sinon
:
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();
});
});
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 })
);
consthttppromise=(code)=>newpromise((解析,拒绝)=>
(code>=200&&code我发现sinon.stub()返回($q.when({})),这是一个非常有用的插入式服务函数:
控制器:
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' );
代码片段:
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');
});
myService.makeRemoteCall()
上真的有错误吗?如果是这样,问题在于myService
没有makeRemoteCall
,与模拟的myOtherService
没有任何关系。错误在myService.makeRemoteCall()上,因为myService.myOtherService此时只是一个空对象(angular从未更新过其值)您将空对象添加到ioc容器中,然后您将引用myOtherServiceMock更改为指向您监视的新对象。随着引用的更改,ioc容器中的内容不会反映这一点。您昨天因为没有在结果中显示而杀了我。和CallFake的漂亮显示()。谢谢。您可以使用Jasmine 2.0+中的和.returnValue(deferred.promise)
(或和.returnValue(deferred.promise)
来代替和callfake
)。当然,在调用spyOn
之前,您需要定义延迟
。在这种情况下,当您无法访问作用域时,您将如何调用$digest
?JimAho通常您只需注入$rootScope
并调用$digest
。在这种情况下,使用延迟是不必要的。您只需使用$q即可。when()
注意使用Jasmine 2.0的人,.andReturn()已被.and.returnValue替换。因此上面的示例是:spyOn(myOtherService,“makeRemoteCallReturningPromise”).and.returnValue($q.when({}))
我花了半个小时才弄明白。我过去就是这样做的。创建一个间谍,返回一个模仿“then”的假消息你能提供一个完整测试的例子吗?我有一个类似的问题,就是让一个服务返回一个承诺,但它也会发出一个返回承诺的调用!嗨,Rob,我不确定你为什么要模拟模拟一个模拟对另一个服务的调用。在测试该函数时,你肯定会想测试它。如果ion调用您正在模拟调用一个服务获取数据然后影响该数据您模拟的承诺将返回一个假受影响的数据集,至少我会这样做。我沿着这条路径开始,它适用于简单场景。我甚至创建了一个模拟链接并提供“保持”/“中断”的模拟帮助者在更复杂的场景中调用链,但这一点失败了。在我的例子中,我有一个服务,可以有条件地从缓存返回项目(w/deferred)或发出请求。因此,它创建了自己的承诺。这篇文章描述了fake“then”的用法完整地说。每次测试前调用的全部要点是在每次测试前调用它。我不知道您是如何编写测试的,但就我个人而言,我为一个函数编写了多个测试,因此我会在每次测试前调用一个公共基组。此外,您可能希望在反模式关联时查找其理解的含义这是缺少的部分,调用$rootScope.$digest()
,以获得要解决的承诺
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');
});