Anche se la risposta sopra probabilmente funziona bene (non l'ho provata :)), spesso ho molti più test da eseguire, quindi non inserisco i test stessi. Raggrupperò i casi it () in blocchi di descrizione ed eseguirò la mia iniezione in un beforeEach () o beforeAll () in ogni blocco di descrizione.
Robert ha anche ragione in quanto afferma che è necessario utilizzare l'iniettore $ angolare per rendere i test a conoscenza del servizio o della fabbrica. Angular usa questo stesso iniettore anche nelle tue applicazioni, per dire all'applicazione cosa è disponibile. Tuttavia, può essere chiamato in più di un posto e può anche essere chiamato implicitamente anziché esplicitamente. Noterai nel mio file di test delle specifiche di esempio di seguito, il blocco beforeEach () chiama implicitamente l'iniettore per rendere disponibili le cose da assegnare all'interno dei test.
Tornando al raggruppamento delle cose e all'utilizzo dei blocchi precedenti, ecco un piccolo esempio. Sto creando un servizio Cat e voglio testarlo, quindi la mia semplice configurazione per scrivere e testare il servizio sarebbe simile a questo:
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: Cats Controller
'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: Servizio per gatti
'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!"]);
})
});
})
});
MODIFICA
Sulla base di alcuni commenti, ho aggiornato la mia risposta per renderla leggermente più complessa e ho anche creato un Plunkr dimostrativo di Unit Testing. In particolare, uno dei commenti menzionava "E se il servizio di un controller avesse in sé una semplice dipendenza, come $ log?" - che è incluso nell'esempio con i casi di test. Spero che sia d'aiuto! Prova o hackera il pianeta !!!
https://embed.plnkr.co/aSPHnr/