J'ai myService
qui utilise myOtherService
, qui effectue un appel distant en retournant la promesse:
angular.module('app.myService', ['app.myOtherService'])
.factory('myService', [
myOtherService,
function(myOtherService) {
function makeRemoteCall() {
return myOtherService.makeRemoteCallReturningPromise();
}
return {
makeRemoteCall: makeRemoteCall
};
}
])
Pour effectuer un test unitaire pour myService
, je dois me moquer de myOtherService
, de sorte que sa méthode makeRemoteCallReturningPromise
renvoie une promesse. Voici comment je le fais:
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');
});
}));
Comme vous pouvez le voir ci-dessus, la définition de ma maquette dépend de $q
, que je dois charger avec inject()
. En outre, l'injection de la maquette doit être effectuée dans module()
, qui doit précéder inject()
. Cependant, la valeur de la maquette n'est pas mise à jour une fois que je l'ai modifiée.
Quelle est la bonne façon de faire cela?
Je ne sais pas pourquoi votre méthode ne fonctionne pas, mais je le fais habituellement avec la fonction spyOn
. Quelque chose comme ça:
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');
});
}));
Rappelez-vous également que vous devrez appeler $digest
pour que la fonction then
soit appelée. Voir la section Testing de la documentation $ q .
------ EDIT ------
Après avoir examiné de plus près ce que vous faites, je pense que je vois le problème dans votre code. Dans le beforeEach
, vous définissez myOtherServiceMock
sur un nouvel objet. Le $provide
ne verra jamais cette référence. Il vous suffit de mettre à jour la référence existante:
beforeEach(inject( function(_myService_, $q){
myService = _myService_;
myOtherServiceMock.makeRemoteCallReturningPromise = function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
};
}
Nous pouvons également écrire l'implémentation de jasmine de "return promise" directement par espion.
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
Pour Jasmine 2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(copié des commentaires, merci à 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();
});
});
Vous pouvez utiliser une librairie stubbing telle que sinon pour se moquer de votre service. Vous pouvez ensuite retourner $ q.when () comme votre promesse. Si la valeur de votre objet scope provient du résultat de la promesse, vous devrez appeler 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);
});
en utilisant sinon
:
const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
.returns(httpPromise(200));
Sait que, httpPromise
peut être:
const httpPromise = (code) => new Promise((resolve, reject) =>
(code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);
Honnêtement ... vous vous y prenez de la mauvaise façon en vous fiant à injecter pour simuler un service au lieu d'un module. Également, appeler inject dans beforeEach est un anti-modèle, car il est difficile de se moquer par test.
Voici comment je le ferais ...
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;
};
});
});
Désormais, lorsque vous injecterez votre service, la méthode d’utilisation sera correctement simulée.
J'ai trouvé cette fonction de service poignante utile comme sinon.stub (). Return ($ 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 );
manette:
this.someMethod = function(someObj) {
myService.myFunction( someObj ).then( function() {
someObj.loaded = 'bla-bla';
}, function() {
// failure
} );
};
et test
const obj = {
field: 'value'
};
this.ctrl.someMethod( obj );
this.scope.$digest();
expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );
L'extrait de code:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
Peut être écrit sous une forme plus concise:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
return $q.resolve('Remote call result');
});