Тестирование в браузере упрощает разработку веб-приложений. Но как имитировать зависимости модулей в браузере?

Тестирование в браузере экономит время, особенно при разработке прототипа. Для несложных модулей, например связанных с пользовательским интерфейсом, можно завершить код, а затем написать тесты.

Но представьте себе модуль, выполняющий сложные вычисления. Например, модуль, который преобразует и объединяет некоторые сложные статистические данные в простые данные, которые можно отобразить на диаграмме. Во время разработки сложных вычислений, которые вы не можете проделать в уме, неизбежны многочисленные отладки, и поэтому необходимы частые тесты.

Бывает, что модули нужно смоделировать, чтобы облегчить или даже сделать возможным тестирование модулей, импортирующих их. Типичными целями для имитации являются модули, извлекающие данные из ресурсов REST. Данные, которые они возвращают, могут различаться в зависимости от запроса. Кроме того, в зависимости от приложения извлекаемые данные могут быть огромными и требовать много времени для загрузки и обработки.

Проще интерпретировать и отлаживать тесты, если тестируемый код отделен от любых внешних ресурсов, которые могут потребовать времени и внести случайность. В имитируемом модуле fetch вызовы заменяются некоторыми постоянными данными, чтобы гарантировать, что во время тестирования родительский модуль всегда получает один и тот же ввод, который должен приводить к одному и тому же выводу.

Мокинг модуля с вызовами выборки

Представьте себе модуль, который производит невообразимо сложные вычисления на огромных и неразборчивых входных данных и производит простой вывод, который в конечном итоге отображается пользователям. В этом посте я смоделирую такой модуль с помощью крошечного образца модуля userView.js. Предположим, он преобразует данные профиля пользователя в компонент панели мониторинга:

// userView.js
import { getUser } from "shared/dataSource.js";
export default async function getHomeView() {
    const user = await getUser();
    return `Hello ${user.name}`;
}

Для простоты все воображаемые сложные вычисления над пользовательскими данными заменяются примитивной строкой шаблона `Hello ${user.name}`, тогда как огромные данные профиля пользователя заменяются крошечным JSON {"name": "John"}. Модуль userView.js получает данные из импортированной функции getUser(). Функция извлекает данные из ресурса REST:

// dataSource.js
export function getUser() {
    return fetch('api/user.json').then(response => response.json());
}

Предполагая, что данные, относящиеся к конкретному пользователю, полученные из ресурса REST, зависят от каждого запроса, как проверить вывод userView.js? Начнем с поверхностного теста userView.js:

// userViewTest.js
import getHomeView from './userView.js';
 
describe("homeView", function () {
    it("returns some content, but irreproducible depending on REST resource", async function () {
        const view=await getHomeView();
        expect(view).toBeDefined();
    });
});

Поскольку ресурс REST отвечает разным содержимым и, следовательно, getHomeView() возвращает разный результат, я могу только проверить, создал ли модуль что-то expect(view).toBeDefined(). Но я не могу знать, что именно производится.

Идеальный тест должен включать полные и тщательные проверки:

// userViewWithMockTest.js
import getHomeView from './userView.js';
 
describe("homeView", function () {
    it("expected output of complex calculations", async function() {
        const view=await getHomeView();
        expect(view).toBe('Hello Mock');
    });
});

Чтобы можно было использовать идеальный тест, описанный выше, импортированная функция getUser() должна возвращать постоянные данные {"name": "Mock"}. Для этого я написал фиктивный модуль dataSourceMock.js, который заменит модуль dataSource.js во время тестов. Имитируемый модуль не fetch, вместо этого он возвращает Promise, который разрешается в постоянное значение.

// dataSourceMock.js
export function getUser() {
    return Promise.resolve({"name":"Mock"});
}

Чтобы заменить исходные модули на их макет во время тестов, я добавил карту импорта в SpecRunner.html Jasmine:

<script type="importmap">
    {
        "imports": {
            "shared/dataSource.js":"./js/shared/dataSourceMock.js"
        }
    }
</script>

Карта импорта указывает браузеру, как имя модуля, включенное в оператор import { getUser } from "shared/dataSource.js"; тестируемого модуля userView.js, должно быть преобразовано в фактический URL-адрес. Теперь, когда браузер встречает имя shared/dataSource.js, он импортирует привязки имитируемого модуля dataSourceMock.js.

Несмотря на то, что другого способа имитировать модули в браузере нет, тесты не являются основным вариантом использования импорта карт. Импорт карт - это функция, которая расширила возможности модулей JavaScript. Без карт импорта модули JavaScript было довольно сложно использовать в браузере из-за их import с относительными URL-адресами, которые могли быть длинными и заполненными двойными точками. По крайней мере, по сравнению с неродной модульной системой AMD, нативные модули казались весьма несовершенными.

Полный исходный код этого поста можно скачать с GitHub и запустить в браузере.

Больше контента на plainenglish.io