Модульное тестирование действий и эффектов

Hyperapp строится вокруг чистых функций состояния и действий, что делает его особенно удобным для модульного тестирования. В архитектуре Hyperapp есть три ключевых элемента: state (состояние), actions (действия) и effects (эффекты). Каждый из них можно тестировать независимо, что позволяет создавать надёжные и предсказуемые приложения.


Тестирование действий (Actions)

Действия в Hyperapp — это функции, которые принимают текущее состояние и возвращают новое состояние либо объект с эффектами. Ключевая особенность для тестирования — чистота функций.

Пример простого действия

const increment = state => ({ ...state, count: state.count + 1 });

Для тестирования такой функции используется прямое сравнение результатов:

const state = { count: 0 };
const nextState = increment(state);

console.assert(nextState.count === 1, "Count должен увеличиться на 1");

Тестирование вложенных действий

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

const addTodo = (state, title) => ({
  todos: [...state.todos, { title, done: false }],
});

const state = { todos: [] };
const nextState = addTodo(state, "Купить хлеб");

console.assert(nextState.todos.length === 1, "Должен появиться новый элемент списка");
console.assert(nextState.todos[0].title === "Купить хлеб", "Заголовок должен совпадать");

Проверка неизменяемости состояния

Hyperapp предполагает, что действия не мутируют исходный объект состояния:

console.assert(state.todos.length === 0, "Исходное состояние не должно изменяться");

Тестирование эффектов (Effects)

Эффекты в Hyperapp предназначены для взаимодействия с внешним миром: сетевые запросы, таймеры, LocalStorage и т.д. Эффекты не должны напрямую менять состояние, поэтому их тестирование отличается от тестирования действий.

Создание эффектов

const logEffect = message => (dispatch, props) => {
  console.log(props.message);
};

Мокирование эффектов

Для тестов эффекты часто заменяют фейковыми функциями, чтобы избежать реального воздействия на систему:

let logged = null;
const mockEffect = message => (dispatch, props) => {
  logged = props.message;
};

mockEffect("Hello")(null, { message: "Hello" });

console.assert(logged === "Hello", "Сообщение должно быть записано через эффект");

Проверка вызова эффектов из действий

Если действие возвращает объект с эффектом, тестирование выглядит так:

const showMessage = state => [
  state,
  logEffect("Привет!")
];

const [nextState, effect] = showMessage({});

let messageLogged = null;
effect(null, { message: "Привет!" });
console.assert(messageLogged === null, "В тесте можно перехватывать эффект");

Интеграционное тестирование действий с эффектами

Иногда требуется проверить, что действие корректно вызывает эффект с нужными параметрами:

const fetchData = () => (state) => [
  state,
  (dispatch, props) => {
    dispatch({ type: "dataLoaded", data: [1, 2, 3] });
  }
];

let dispatched = null;
const mockDispatch = action => { dispatched = action; };

const [newState, effect] = fetchData()({});
effect(mockDispatch);

console.assert(dispatched.type === "dataLoaded", "Должен быть вызван action dataLoaded");
console.assert(Array.isArray(dispatched.data), "Данные должны быть массивом");

Использование библиотек для тестирования

Для автоматизации тестов в Hyperapp можно использовать Jest, Mocha или Vitest. Они позволяют проверять:

  • Возвращаемое состояние действий
  • Вызовы эффектов и их параметры
  • Обновление состояния без мутаций

Пример с Jest:

test("increment увеличивает count", () => {
  const state = { count: 0 };
  const nextState = increment(state);
  expect(nextState.count).toBe(1);
  expect(state.count).toBe(0);
});

Рекомендации по организации тестов

  1. Разделять тесты на Actions и Effects — это упрощает понимание и поддержку.
  2. Использовать мок-объекты для внешних вызовов (fetch, console, LocalStorage).
  3. Проверять неизменяемость состояния, чтобы избежать скрытых багов.
  4. Писать маленькие чистые действия, чтобы они легко тестировались независимо от эффекта.
  5. Покрывать критические сценарии, особенно цепочки действий и ошибки эффектов.

Практика TDD в Hyperapp

Hyperapp идеально подходит для разработки через тестирование (TDD). Сначала пишется тест для действия или эффекта, затем реализуется функция. Такой подход гарантирует:

  • предсказуемое поведение приложения;
  • лёгкость в рефакторинге;
  • минимизацию багов при сложной логике состояния.

Тесты становятся не вспомогательным инструментом, а неотъемлемой частью архитектуры, обеспечивая стабильность и масштабируемость приложений.