глобальная имитация
в angular.js есть удобный механизм инъекции зависимостей. Среди прочего он позволяет довольно удобным образом подменять эти зависимости имитациями при модульном тестировании: достаточно написать $provide.value('name', mockedDependency)
. Ну и создать эту имитацию тоже нужно.
однако в большом проекте часто не всё оказывается так гладко, и проявляются проблемы этого «наивного» подхода. Во-первых, программист может добавить в модуль новую зависимость, но забыть подменить её имитацией в тесте. Во-вторых, имитации активно используемых модулей дублируются в десятках тестов. В третьих, неопытные программисты часто будут подгружать в тест реальную зависимость, и навешивать заглушку только на одну её функцию. Соответственно, остальные её функции останутся реальными, да и заодно будут инициализированы зависимости второго и следующих уровней.
я вдохновился тем, как разработчики самого angular.js создали отдельный файл с имитациями популярных модулей — angular-mocks.js. (Достаточно подключить его в страницу тестов, и работать с таймаутами, HTTP и обещаниями станет намного проще.) Нам было нужно что-то вроде этого, но для всех наших модулей. Для директив и контроллеров всё очевидно, но вот тестировать модули, для которых заглушки уже есть, так не получится: тесты будут ожидать реальных результатов от имитации. Поэтому нашу глобальную имитацию использовать слегка сложнее: помимо подключения файла нужно ещё указать, что именно будет имитироваться. Например, в начале теста директивы, для которой нет заглушки, мы пишем beforeEach(NS.mocks.all())
. А вот в начале теста модуля routing
, для которого есть заглушка, ставится beforeEach(NS.mocks.except('routing'))
.
NS.mocks.all = function() {
return function() {
module(function($provide) {
Object.keys(NS.mocks.factories).forEach(function(mockName) {
var mockInstance = NS.mocks.factories[mockName]();
$provide.value(mockName, mockInstance);
});
});
};
};
NS.mocks.except = function(excludedName) {
if (!(excludedName in NS.mocks.factories)){
throw new Error("Don't forget to implement mock for " + excludedName + " in mocks.js");
}
return function() {
module(function($provide) {
var mocks = Object.keys(NS.mocks.factories).filter(function(name) {
return (name !== excludedName);
});
mocks.forEach(function(mockName) {
var mockInstance = NS.mocks.factories[mockName]();
$provide.value(mockName, mockInstance);
});
});
};
};
NS.mocks.factories.routing = function() {
return jasmine.createSpyObj('routing', ['to', 'from']);
};