Асинхронность является неотъемлемой частью современных
веб-приложений. В Mithril асинхронные операции чаще всего связаны с
HTTP-запросами, таймерами и
обработкой событий, что делает тестирование критически
важным для стабильности приложения. Важной особенностью является то, что
Mithril предоставляет собственный m.request, который
возвращает промис, и корректное тестирование требует понимания работы
промисов и механизма виртуального DOM.
m.request и
промисыm.request используется для выполнения HTTP-запросов. Он
возвращает объект, совместимый с промисами:
m.request({
method: "GET",
url: "/api/data"
}).then(response => {
console.log(response);
});
Ключевой момент при тестировании — ожидание завершения промиса. В синхронном тесте промис может не успеть выполниться до завершения теста, что приведет к ложным отрицательным результатам.
async/await в тестахСовременные тестовые фреймворки (Jest, Mocha, Ava) позволяют
использовать async/await, что упрощает работу с
асинхронными операциями:
import m from "mithril";
test("загрузка данных с сервера", async () => {
const data = await m.request({ method: "GET", url: "/api/data" });
expect(data).toHaveProperty("id");
});
Использование await гарантирует, что проверка данных
произойдет после завершения запроса, исключая состояние
гонки.
Для тестирования без реального сервера используется мокаут
сетевых вызовов. В Jest это реализуется через
jest.mock или библиотеки типа fetch-mock:
import m from "mithril";
import fetchMock from "fetch-mock";
afterEach(() => fetchMock.restore());
test("получение данных через моки", async () => {
fetchMock.get("/api/data", { id: 1, name: "test" });
const result = await m.request({ method: "GET", url: "/api/data" });
expect(result.id).toBe(1);
});
Преимущество мокаутов — тесты становятся детерминированными и не зависят от внешних сервисов.
Mithril компоненты могут выполнять асинхронные действия при
инициализации или в методах жизненного цикла (oninit,
oncreate):
const Component = {
data: null,
oninit: async function() {
this.data = await m.request({ method: "GET", url: "/api/data" });
},
view: function() {
return m("div", this.data ? this.data.name : "Загрузка...");
}
};
Тестирование такого компонента требует ожидания завершения асинхронного кода перед проверкой состояния DOM:
import { render, screen } from "@testing-library/dom";
import m from "mithril";
test("компонент отображает данные после загрузки", async () => {
fetchMock.get("/api/data", { name: "Mithril" });
const root = document.createElement("div");
m.mount(root, Component);
// Подождать, пока асинхронный код выполнится
await new Promise(resolve => setTimeout(resolve, 0));
expect(root.textContent).toContain("Mithril");
});
Использование setTimeout с нулевой задержкой позволяет
отложить проверку до очереди выполнения промисов, что
гарантирует корректное состояние компонента.
Асинхронные операции могут завершаться ошибкой. Для тестирования
сценариев ошибок нужно использовать конструкцию try/catch
или методы тестового фреймворка для проверки исключений:
test("обработка ошибки запроса", async () => {
fetchMock.get("/api/data", 500);
let error;
try {
await m.request({ method: "GET", url: "/api/data" });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
Важно проверять правильность обработки ошибок в компоненте, чтобы UI не ломался при неудачных запросах.
В Mithril асинхронные события могут использовать
setTimeout или setInterval. Для тестирования
удобно использовать фиктивные таймеры (Jest
jest.useFakeTimers()):
jest.useFakeTimers();
test("таймер обновляет данные", () => {
const component = {
count: 0,
oncreate: function() {
setInterval(() => this.count++, 1000);
},
view: function() {
return m("span", this.count);
}
};
const root = document.createElement("div");
m.mount(root, component);
jest.advanceTimersByTime(3000);
expect(component.count).toBe(3);
});
Использование фиктивных таймеров позволяет контролировать ход времени и мгновенно проверять асинхронные действия без реальных задержек.
Mithril применяет виртуальный DOM, и асинхронные
обновления должны корректно отражаться на отображении. В тестах важно
перемонтировать компонент или использовать
m.redraw.sync() после изменения состояния:
component.data = { name: "test" };
m.redraw.sync();
expect(root.textContent).toContain("test");
Это гарантирует, что все асинхронные изменения состояния корректно отображены в DOM до проверки.
async/await или явное ожидание
промисов.m.redraw.sync() для синхронизации с виртуальным DOM.