DOM API, частичные имитации и развлечения
В последнее время я работал над добавлением дополнительных модульных тестов в интерфейсный код. Использование замечательной библиотеки тестирования реакции Кента С. Доддса (если вы еще не проверили ее, серьезно 🔥) вместе с Jest. Тестировать небольшие компоненты обычно довольно просто. Передайте им смоделированные функции в качестве реквизита, активируйте некоторые события и убедитесь, что все выглядит так, как вы expect
. Больше всего я забавлялся, когда мне нужно было имитировать зависимости.
Краткий призыв к написанию модульных тестов, таких как производственный код
Прежде чем я углублюсь в детали насмешек, я просто хочу написать короткую петицию, чтобы относиться к коду, который вы пишете в модульных тестах, как к коду, который вы пишете для производства. В основном, сделайте его читабельным! Можно действительно легко относиться к модульным тестам как к маленьким взломанным скриптам, а не как к тому, чем они являются на самом деле; фрагменты кода, важные для развертывания вашего приложения.
И что еще более важно, у вас, вероятно, есть другие люди, которым когда-нибудь понадобится разбираться в модульных тестах. Только по этой причине подумайте о том, чтобы разложить их на легко понимаемые фрагменты кода. Напишите вспомогательные функции для инкапсуляции общих фрагментов кода или настройки. Задокументируйте, что происходит. Поскольку модульные тесты - это то, с чем вы обычно взаимодействуете, только когда они терпят неудачу, приятно иметь возможность вникнуть в них и быстро понять, что происходит.
Мокинг DOM API
Иногда API-интерфейсы DOM недоступны в JSDOM. Jest называет это в своей документации, рекомендуя просто написать фиктивную функцию и назначить ее объекту окна. Но что, если вы хотите поиздеваться над недвижимостью в окне?
Оказывается, есть простой способ VanillaJS ™ сделать это. Используя Object.defineProperty
с объектом окна (или объектом документа), мы можем имитировать геттеры и сеттеры. Вот пример, который я недавно написал для издевательства над document.cookie
. Цель состояла в том, чтобы протестировать ситуацию, когда doucment.cookie
не может быть установлен из-за ограничений iframe для разных источников в Safari (🙃).
test('Renders help message when cookies cannot be set', () => { Object.defineProperty(document, 'cookie', { get: jest.fn().mockImplementation(() => { return ''; }), set: jest.fn().mockImplementation(() => {}), }); // more test code... })
Теперь тест делает то, что я хочу. Компонент не сможет установить cookie и должен вести себя правильно.
Альтернативный подход: служебные функции DOM
Другой способ сделать это - вместо того, чтобы напрямую издеваться над document.cookie
, мы могли бы написать служебную функцию для компонента, который ее использует. function canSetCookie() -> boolean
Почему это полезно? Потому что нам совсем не нужно высмеивать свойство document
. Вместо этого мы можем просто имитировать наш служебный метод, как будто мы имитируем что-нибудь еще в тесте. Например, если функция canSetCookie
используется как опора, у нас есть простой способ протестировать этот компонент изолированно.
Или, если он находится в каком-то общем локальном служебном файле, мы можем имитировать эту зависимость. Наряду с кодом cookie у нас также был код, который определял, находимся ли мы в iframe. Поскольку это было во вспомогательном методе, я мог сделать «нормальный» макет со следующим кодом.
jest.mock('shared/util/iframe.js', () => ({ isInIframe: () => true }));
Легко, дальше.
Частично имитация зависимостей
Допустим, у вас есть зависимость, в которой вы хотите по-разному имитировать функцию для каждого теста. Например, ситуация, в которой мне нужно это сделать, - это клиентский код. У нас есть клиенты API, которые могут пройти тест по многим маршрутам. Для многих тестов мы хотим вернуть одно и то же из наших моков, но иногда мы можем захотеть протестировать разные сценарии (например, API, возвращающий ответы об ошибках). Вопрос в том, как у нас есть общий макет, а затем время от времени переопределять отдельные экспорты из него? Или, может быть, вы хотите понаблюдать, с чем вызывается фиктивная функция?
Ответ здесь тоже оказывается просто javascript. Шаги следующие:
- Насмехайтесь над зависимостью
- Импортируйте фиктивную зависимость
- Назначьте функцию, которую мы хотим переопределить, обратно на импорт
Например:
jest.mock('shared/my-api-client.js'); import { client } from 'shared/my-api-client.js'; test('returns error from client' () => { const mockMethod = jest.fn(() => Promise.resolve({})); client.sendInfo = mockMethod; // test code... expect(mockMethod).toHaveBeenCalledWith(expectedJson); })
При этом следует помнить одну важную вещь: вам нужно будет очищать клиентский метод, над которым вы насмехались, после каждого теста, который касается этого метода.
Я надеюсь, что это было полезно! Это всего лишь несколько приемов, которые я нашел очень полезными при написании модульных тестов для сложных компонентов. Если у вас есть какие-то техники, которые вы сочтете полезными, я хотел бы услышать о них ниже.