Предыдущая часть этой статьи здесь , но она не обязательна для понимания текущей темы.
Модульное тестирование — один из лучших инструментов, которые у меня есть в моей коробке, чтобы помочь в разработке моего кода. Тем не менее, я не всегда нахожусь в среде, где модульное тестирование легко: зрелые фреймворки JavaScript и изоляция тестов представляют те же проблемы других языков: глобальное состояние и внешние взаимодействия (плюс асинхронное поведение).
Одним из очевидных внешних взаимодействий являются Ajax-вызовы, выполняемые любой библиотекой, которую вы используете. Эта статья рассказывает о том, как мы получили возможность тестировать клиентский код JavaScript, заглушая вызовы Ajax; хотя затем мы перешли к сквозному подходу к тестированию по другой причине, по крайней мере, это ноу-хау будет сохранено здесь и не будет потеряно.
механика
JavaScript является своего рода открытым языком в смысле открытых классов Ruby. Вы можете заменить практически любой метод, предоставляемый общедоступным API:
jQuery.val = function() { ... };
Эта способность означает, что если мы знаем, какая библиотека вызывает вызываемый тестируемый код, мы можем протестировать ее, даже если она не была разработана с помощью Dependency Injection, как мы предпочитаем в PHP или Java. Поскольку части кода могут даже не находиться под нашим контролем, это часто единственный выбор.
Эта форма изоляции достигается путем вмешательства в глобальное состояние, поэтому она может быть опасной. Фазы демонтажа становятся основополагающими, по крайней мере, до тех пор, пока среды тестирования не станут лучше в песочнице тестов.
Заглушка библиотеки звонков
Первый выбор для тестирования кода, который выполняет вызовы Ajax, — это заглушка используемого библиотечного метода. Если вы находитесь в 0,01% людей, которые непосредственно создают экземпляры объектов XMLHttpRequest, легко превратить это создание в метод, который вы можете позже заглушить.
Для jQuery целью является метод jQuery.ajax. В случае Ext JS это Ext.Ajax.request. Однако в последнем случае Ext.Ajax.request вызывается с большим количеством различных опций другими компонентами Ext JS, что будет сложно написать заглушку общего назначения.
Механика следующая:
- сохранить старый метод в переменной ( var oldAjax = jQuery.ajax; )
- Замените его закрытием, которое сохраняет обратный вызов в переменной. Это замыкание может проверять свои аргументы (становится своего рода насмешкой).
- Используйте обратный вызов, чтобы передать обратно готовый ответ , написанный в буквальном виде JSON, XML или что-либо, что вы возвращаете со стороны сервера.
- Восстановите старый метод (фаза разрыва) с новым назначением.
Заглушка XMLHttpRequest
Альтернативный способ заглушить вызовы Ajax — это остановить создание XMLHttpRequest. Поскольку XMLHttpRequest — это просто глобальный объект класса Function, его можно заменить. Более того, вам не нужно делать это вручную: Sinon — это библиотека JavaScript, которая делает именно то, что нам нужно . Это работает также в Проводнике, так как проверяет также ActiveXObject; он обеспечивает Api более высокого уровня, так что вам не нужно переписывать весь объект и все его методы.
Sinon предоставляет различные методы тестирования, которые позволяют использовать более одного рабочего процесса: список соответствий между URL-адресами и ответами или отдельные ожидания. Это просто файл .js, поэтому он будет работать с любой используемой средой тестирования:
var fakeXhr = sinon.useFakeXMLHttpRequest(); var requests = []; fakeXhr.onCreate = function (xhr) { requests.push(xhr); }; jQuery.ajax({ url: "/my/page" }); // this should go in an always executed tearDown method fakeXhr.restore(); assertEquals("/my/page", this.requests[0].url);
Когда нужно смоделировать несколько вызовов Ajax, Api немного меняется:
var server = sinon.fakeServer.create(); server.respondWith("responseText content"); // also configurable by URL var called = false; callback = function(text) { assertEquals("responseText content", text); called = true; }; jQuery.ajax({ url: "/my/page", success: callback }); server.respond(); // should go in the tearDown phase server.restore(); assertTrue(called);
Вызов server.respond () устраняет асихронность теста: после этого вы уверены, что были вызваны функции обработчика Ajax. Вы можете продолжить делать утверждения или продолжить тестирование.
Ext JS в настоящее время требует большего шага, так как он проверяет с помощью опроса, завершен ли запрос, а не полагается на обратные вызовы (не спрашивайте меня, почему): setInterval также должен быть заглушен.
var oldSetInterval = setInterval; callbacks = []; setInterval = function (callback, delay) { callbacks.push(callback); }; // stub and call code under test as before... setInterval = oldSetInterval; for (i in callbacks) { callbacks[i].call(); } // assertions...
На этот раз callbacks [i] .call () избегает асинхронности: обработчики Ext, которые должны проверять текст и код ответа, вызываются сразу после выполнения Ajax-заглушки.
Игра с setTimeout, setInterval и другими временными функциями может быть опасной, если среда тестирования не поддерживает ее; это может вызывать их для внутренних целей, например, чтобы избежать бесконечного теста. Эта проблема была главной в jsTestDriver, но была решена в 2009 году : теперь jsTestDriver создает свои собственные копии setInterval и других чувствительных функций перед началом запуска тестовых случаев. Если вы используете jsTestDriver, вы можете играть с этими глобальными функциями.
Выводы
Тестирование кода JavaScript всегда возможно, и когда задействовано больше логики, чем Ajax-вызов, заглушка помогает изолировать тестируемую систему. Тестирование с использованием реальных вызовов Ajax невозможно даже в моей любимой среде jsTestDriver, поскольку код подается с веб-сервера на локальном хосте, а не из реального приложения. Действительно, решения, описанные в этой статье, подходят для модульного тестирования, а не сквозного или функционального.