глобальная имитация

в 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']);
};

Артемий Трегубенко,
,

comments powered by Disqus