Тесты для Effector обычно описывают с помощью Fork API.
Fork создает независимый контекстный экземпляр, который мы можем использовать для эмуляции некоторых конкретных ситуаций или окружений.
Пример счетчика
Например, у нас есть типичный счетчик, но с асинхронной проверкой через наш бэкэнд. Предположим, у нас следующие требования:
- Когда пользователь нажимает кнопку, мы проверяем, меньше ли текущий счетчик чем 100, и затем проверяем этот клик через наш API бэкэнда.
- Если валидация успешна, увеличиваем счетчик на 1.
- Если проверка не пройдена, нужно сбросить счетчик до нуля.
import {createEvent, createStore, createEffect, sample} from 'effector'
export const buttonClicked = createEvent()
export const validateClickFx = createEffect(async () => (/* some api call */))
export const $clicksCount = createStore(0)
sample({
source: $clicksCount,
clock: buttonClicked,
filter: count => count < 100,
target: validateClickFx,
})
sample({
source: $clicksCount,
clock: validateClickFx.done,
fn: count => count + 1,
target: $clicksCount,
})
sample({
clock: validateClickFx.fail,
fn: () => 0,
target: $clicksCount,
})
Настройка тестов
Наш основной сценарий следующий:
- Пользователь нажимает на кнопку.
- Валидация заканчивается успешно.
- Счетчик увеличивается на 1.
Давайте протестируем это:
- Создадим новый экземпляр Scope посредством вызова
fork
. Мы можем рассматривать его как независимый экземпляр нашего приложения Effector. - Проверим, что изначально счет равен
0
. - Затем сымитируем событие
buttonClicked
с использованиемallSettled
– этот промис будет разрешен после завершения всех вычислений. - Проверим, что в конце у нас имеется нужное состояние.
import { fork, allSettled } from "effector";
import { $clicksCount, buttonClicked, validateClickFx } from "./model";
test("main case", async () => {
const scope = fork(); // 1
expect(scope.getState($clicksCount)).toEqual(0); // 2
await allSettled(buttonClicked, { scope }); // 3
expect(scope.getState($clicksCount)).toEqual(1); // 4
});
Кастомные обработчики эффектов
Однако в этом тесте есть проблема — он использует реальный API бэкенда. Но поскольку это юнит тест, нам следует каким-то образом подменить этот запрос.
Мы можем предоставить кастомный обработчик через конфигурацию fork
.
test("main case", async () => {
const scope = fork({
handlers: [
// Список пар [effect, mock handler]
[validateClickFx, () => mockResponse],
],
});
expect(scope.getState($clicksCount)).toEqual(0);
await allSettled(buttonClicked, { scope });
expect(scope.getState($clicksCount)).toEqual(1);
});
Кастомные значения стора
У нас есть еще один сценарий:
- Счетчик уже больше 100.
- Пользователь нажимает кнопку.
- Должен отсутствовать вызов эффекта.
Для этого случая нам потребуется как-то подменить начальное состояние «больше 100» каким-то образом.
Мы также можем предоставить кастомное начальное значение через конфигурацию fork
.
test("bad case", async () => {
const MOCK_VALUE = 101;
const mockFunction = testRunner.fn();
const scope = fork({
values: [
// Список пар [store, mockValue]
[$clicksCount, MOCK_VALUE],
],
handlers: [
// Список пар [effect, mock handler]
[
validateClickFx,
() => {
mockFunction();
return mockResponse;
},
],
],
});
expect(scope.getState($clicksCount)).toEqual(MOCK_VALUE);
await allSettled(buttonClicked, { scope });
expect(scope.getState($clicksCount)).toEqual(MOCK_VALUE);
expect(mockFunction).toHaveBeenCalledTimes(0);
});
Вот так мы можем протестировать каждый случай использования, который хотим проверить.
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.