Bien que la réponse ci-dessus fonctionne probablement très bien (je ne l'ai pas essayé :)), j'ai souvent beaucoup plus de tests à exécuter, donc je n'injecte pas les tests eux-mêmes. Je vais regrouper les cas it () en blocs describe et exécuter mon injection dans un beforeEach () ou beforeAll () dans chaque bloc describe.
Robert a également raison en ce sens qu'il dit que vous devez utiliser l'injecteur Angular $ pour informer les tests du service ou de l'usine. Angular utilise également cet injecteur dans vos applications pour indiquer à l'application ce qui est disponible. Cependant, il peut être appelé à plusieurs endroits, et il peut également être appelé implicitement au lieu de explicitement. Vous remarquerez dans mon exemple de fichier de test de spécification ci-dessous, le bloc beforeEach () appelle implicitement injector pour rendre les choses disponibles pour être affectées à l'intérieur des tests.
Pour en revenir au regroupement et à l'utilisation de blocs avant, voici un petit exemple. Je crée un service Cat et je veux le tester, donc ma configuration simple pour écrire et tester le service ressemblerait à ceci:
app.js
var catsApp = angular.module('catsApp', ['ngMockE2E']);
angular.module('catsApp.mocks', [])
.value('StaticCatsData', function() {
return [{
id: 1,
title: "Commando",
name: "Kitty MeowMeow",
score: 123
}, {
id: 2,
title: "Raw Deal",
name: "Basketpaws",
score: 17
}, {
id: 3,
title: "Predator",
name: "Noseboops",
score: 184
}];
});
catsApp.factory('LoggingService', ['$log', function($log) {
// Private Helper: Object or String or what passed
// for logging? Let's make it String-readable...
function _parseStuffIntoMessage(stuff) {
var message = "";
if (typeof stuff !== "string") {
message = JSON.stringify(stuff)
} else {
message = stuff;
}
return message;
}
/**
* @summary
* Write a log statement for debug or informational purposes.
*/
var write = function(stuff) {
var log_msg = _parseStuffIntoMessage(stuff);
$log.log(log_msg);
}
/**
* @summary
* Write's an error out to the console.
*/
var error = function(stuff) {
var err_msg = _parseStuffIntoMessage(stuff);
$log.error(err_msg);
}
return {
error: error,
write: write
};
}])
catsApp.factory('CatsService', ['$http', 'LoggingService', function($http, Logging) {
/*
response:
data, status, headers, config, statusText
*/
var Success_Callback = function(response) {
Logging.write("CatsService::getAllCats()::Success!");
return {"status": status, "data": data};
}
var Error_Callback = function(response) {
Logging.error("CatsService::getAllCats()::Error!");
return {"status": status, "data": data};
}
var allCats = function() {
console.log('# Cats.allCats()');
return $http.get('/cats')
.then(Success_Callback, Error_Callback);
}
return {
getAllCats: allCats
};
}]);
var CatsController = function(Cats, $scope) {
var vm = this;
vm.cats = [];
// ========================
/**
* @summary
* Initializes the controller.
*/
vm.activate = function() {
console.log('* CatsCtrl.activate()!');
// Get ALL the cats!
Cats.getAllCats().then(
function(litter) {
console.log('> ', litter);
vm.cats = litter;
console.log('>>> ', vm.cats);
}
);
}
vm.activate();
}
CatsController.$inject = ['CatsService', '$scope'];
catsApp.controller('CatsCtrl', CatsController);
Spec: Contrôleur de chats
'use strict';
describe('Unit Tests: Cats Controller', function() {
var $scope, $q, deferred, $controller, $rootScope, catsCtrl, mockCatsData, createCatsCtrl;
beforeEach(module('catsApp'));
beforeEach(module('catsApp.mocks'));
var catsServiceMock;
beforeEach(inject(function(_$q_, _$controller_, $injector, StaticCatsData) {
$q = _$q_;
$controller = _$controller_;
deferred = $q.defer();
mockCatsData = StaticCatsData();
// ToDo:
// Put catsServiceMock inside of module "catsApp.mocks" ?
catsServiceMock = {
getAllCats: function() {
// Just give back the data we expect.
deferred.resolve(mockCatsData);
// Mock the Promise, too, so it can run
// and call .then() as expected
return deferred.promise;
}
};
}));
// Controller MOCK
var createCatsController;
// beforeEach(inject(function (_$rootScope_, $controller, FakeCatsService) {
beforeEach(inject(function (_$rootScope_, $controller, CatsService) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
createCatsController = function() {
return $controller('CatsCtrl', {
'$scope': $scope,
CatsService: catsServiceMock
});
};
}));
// ==========================
it('should have NO cats loaded at first', function() {
catsCtrl = createCatsController();
expect(catsCtrl.cats).toBeDefined();
expect(catsCtrl.cats.length).toEqual(0);
});
it('should call "activate()" on load, but only once', function() {
catsCtrl = createCatsController();
spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);
// *** For some reason, Auto-Executing init functions
// aren't working for me in Plunkr?
// I have to call it once manually instead of relying on
// $scope creation to do it... Sorry, not sure why.
catsCtrl.activate();
$rootScope.$digest(); // ELSE ...then() does NOT resolve.
expect(catsCtrl.activate).toBeDefined();
expect(catsCtrl.activate).toHaveBeenCalled();
expect(catsCtrl.activate.calls.count()).toEqual(1);
// Test/Expect additional conditions for
// "Yes, the controller was activated right!"
// (A) - there is be cats
expect(catsCtrl.cats.length).toBeGreaterThan(0);
});
// (B) - there is be cats SUCH THAT
// can haz these properties...
it('each cat will have a NAME, TITLE and SCORE', function() {
catsCtrl = createCatsController();
spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);
// *** and again...
catsCtrl.activate();
$rootScope.$digest(); // ELSE ...then() does NOT resolve.
var names = _.map(catsCtrl.cats, function(cat) { return cat.name; })
var titles = _.map(catsCtrl.cats, function(cat) { return cat.title; })
var scores = _.map(catsCtrl.cats, function(cat) { return cat.score; })
expect(names.length).toEqual(3);
expect(titles.length).toEqual(3);
expect(scores.length).toEqual(3);
});
});
Spec: Service de chats
'use strict';
describe('Unit Tests: Cats Service', function() {
var $scope, $rootScope, $log, cats, logging, $httpBackend, mockCatsData;
beforeEach(module('catsApp'));
beforeEach(module('catsApp.mocks'));
describe('has a method: getAllCats() that', function() {
beforeEach(inject(function($q, _$rootScope_, _$httpBackend_, _$log_, $injector, StaticCatsData) {
cats = $injector.get('CatsService');
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
// We don't want to test the resolving of *actual data*
// in a unit test.
// The "proper" place for that is in Integration Test, which
// is basically a unit test that is less mocked - you test
// the endpoints and responses and APIs instead of the
// specific service behaviors.
mockCatsData = StaticCatsData();
// For handling Promises and deferrals in our Service calls...
var deferred = $q.defer();
deferred.resolve(mockCatsData); // always resolved, you can do it from your spec
// jasmine 2.0
// Spy + Promise Mocking
// spyOn(obj, 'method'), (assumes obj.method is a function)
spyOn(cats, 'getAllCats').and.returnValue(deferred.promise);
/*
To mock $http as a dependency, use $httpBackend to
setup HTTP calls and expectations.
*/
$httpBackend.whenGET('/cats').respond(200, mockCatsData);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
})
it(' exists/is defined', function() {
expect( cats.getAllCats ).toBeDefined();
expect( typeof cats.getAllCats ).toEqual("function");
});
it(' returns an array of Cats, where each cat has a NAME, TITLE and SCORE', function() {
cats.getAllCats().then(function(data) {
var names = _.map(data, function(cat) { return cat.name; })
var titles = _.map(data, function(cat) { return cat.title; })
var scores = _.map(data, function(cat) { return cat.score; })
expect(names.length).toEqual(3);
expect(titles.length).toEqual(3);
expect(scores.length).toEqual(3);
})
});
})
describe('has a method: getAllCats() that also logs', function() {
var cats, $log, logging;
beforeEach(inject(
function(_$log_, $injector) {
cats = $injector.get('CatsService');
$log = _$log_;
logging = $injector.get('LoggingService');
spyOn(cats, 'getAllCats').and.callThrough();
}
))
it('that on SUCCESS, $logs to the console a success message', function() {
cats.getAllCats().then(function(data) {
expect(logging.write).toHaveBeenCalled();
expect( $log.log.logs ).toContain(["CatsService::getAllCats()::Success!"]);
})
});
})
});
EDIT
Sur la base de certains des commentaires, j'ai mis à jour ma réponse pour être légèrement plus complexe, et j'ai également créé un Plunkr démontrant les tests unitaires. Plus précisément, l'un des commentaires a mentionné "Et si le service d'un contrôleur avait lui-même une simple dépendance, telle que $ log?" - qui est inclus dans l'exemple avec des cas de test. J'espère que ça aide! Testez ou piratez la planète !!!
https://embed.plnkr.co/aSPHnr/