Je moque un service pour un test unitaire AngularJS. J'utilise le $provide
service pour remplacer le service "réel" par le service simulé (un script plunker de ceci est disponible):
describe('My Controller', function () {
var $scope;
var $provide;
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.module(function (_$provide_) {
$provide = _$provide_;
}));
beforeEach(angular.mock.inject(function($rootScope, $controller, $q){
var mockMyService = {
getAll : function() {
var deferred = $q.defer();
deferred.resolve([
{ itemText: "Foo" },
{ itemText: "Bar" }
]);
return deferred.promise;
}
};
$provide.value('myService', mockMyService);
$scope = $rootScope.$new();
$controller('MyCtrl', { $scope: $scope });
$rootScope.$apply();
}));
it('Has two items defined', function () {
expect($scope.items.length).toEqual(2);
});
});
Cela fonctionne très bien. Cependant, je n'aime pas le fait que j'utilise un angular.mock.module
fonctionne simplement pour donner une référence à $provide
service qui est ensuite utilisé dans le angular.mock.inject
fonction ci-dessous. Mais si j'ajoute $provide
comme paramètre du angular.mock.inject
fonctionne directement à la place, j'obtiens une erreur "fournisseur inconnu".
Il me semble que je pourrais mettre tout le code moqueur dans le angular.mock.module
une fonction. Mais j'ai un problème similaire avec le $q
référence, dont j'ai besoin car mon service moqué doit retourner une promesse.
En d'autres termes, si j'ajoute un $q
paramètre au angular.mock.module
alors j'obtiens également une erreur "fournisseur inconnu".
Existe-t-il un moyen de simplifier cela? Évidemment, ce que j'ai fonctionne, mais ça ne me semble pas très bien, d'une manière ou d'une autre. Je sens que je ne comprends pas pourquoi certains fournisseurs sont disponibles dans les fonctions inject
et d'autres sont disponibles dans les fonctions module
.
Vous ne pouvez pas utiliser $provide
dans la fonction inject
car le premier enregistre les fournisseurs pour que le second l'utilise. Regarde:
describe('...', function() {
beforeEach(function() {
module(function($provide) {
$provide.constant('someValue', 'foobar');
});
inject(function(someValue) {
var value = someValue; // will be 'foobar';
});
});
});
Vous pouvez cependant rédiger votre test de cette façon:
describe('...', function() {
var serviceMock;
beforeEach(function() {
serviceMock = {
someMethod: function() { ... }
};
module(function($provide) {
$provide.value('service', serviceMock);
});
inject(function(service) {
...
});
});
});
En fait, vous n'avez même pas besoin d'implémenter le service simulé avant de l'injecter avec $provide
:
beforeEach(function() {
serviceMock = {};
module(function($provide) {
$provide.value('service', serviceMock);
});
inject(function(service) {
...
});
});
it('tests something', function() {
// Arrange
serviceMock.someMethod = function() { ... }
// Act
// does something
// Assert
expect(...).toBe(...);
});
Voici un script Plunker illustrant principalement ce qui précède.
Cela a fonctionné pour moi lorsque j'ai dû envelopper un service qui utilisait $q
et semble assez propre:
var _ServiceToTest_;
beforeEach(function () {
module('module.being.tested');
module(function ($provide) {
$provide.factory('ServiceToMock', function ($q, $rootScope) {
var service = ...;
// use $q et al to heart's content
return service;
});
});
inject(function (_ServiceToTest_) {
ServiceToTest = _ServiceToTest_;
});
});
it('...', function () { /* code using ServiceToTest */ });
L'astuce consistait à utiliser $provide.factory
au lieu de $provide.value
.