Тестирование обработчиков

Fresh — это современный фреймворк для разработки фронтенда на основе Deno, ориентированный на реактивные интерфейсы без необходимости использования клиентских бандлеров. В основе Fresh лежит концепция SSR (Server-Side Rendering) с минимальной зависимостью от JavaScript на клиенте, что повышает производительность и снижает сложность фронтенд-приложений.

Одним из ключевых элементов работы с Fresh являются обработчики (handlers) — функции, которые управляют жизненным циклом запросов и формируют ответ для пользователя.


Обработчики в Fresh

Обработчики — это функции, привязанные к определённым маршрутам. Они реализуют логику получения данных, обработки форм, редиректов и других серверных операций. В Fresh обработчик описывается через объект с методами HTTP-запросов:

export const handler = {
  GET(req, ctx) {
    return new Response("Hello, Fresh!");
  },
  POST(req, ctx) {
    const formData = await req.formData();
    const name = formData.get("name");
    return new Response(`Hello, ${name}!`);
  }
};
  • req — объект запроса, предоставляет доступ к параметрам, заголовкам, телу запроса.
  • ctx — контекст маршрута, содержит параметры URL, состояние сессий и другие метаданные.

Методы обработчика должны возвращать Response или промис, который резолвится в Response. Это позволяет легко интегрировать серверные данные с SSR и компонентами Fresh.


Типовые сценарии использования

  1. Получение данных для страницы Обработчики часто используются для подготовки данных перед рендерингом страницы:
export const handler = {
  GET(req, ctx) {
    const data = fetchDataFromDatabase(ctx.params.id);
    return data.then(result => new Response(JSON.stringify(result), {
      headers: { "Content-Type": "application/json" }
    }));
  }
};
  1. Обработка форм Серверный обработчик принимает данные формы, валидирует и сохраняет их:
export const handler = {
  POST(req) {
    const form = await req.formData();
    const email = form.get("email");
    if (!validateEmail(email)) {
      return new Response("Invalid email", { status: 400 });
    }
    saveEmailToDB(email);
    return Response.redirect("/thank-you");
  }
};
  1. Редиректы и ошибки Обработчики позволяют удобно реализовывать редиректы и управление кодами ошибок:
export const handler = {
  GET(req) {
    if (!userIsLoggedIn(req)) {
      return Response.redirect("/login");
    }
    return new Response("Welcome back!");
  }
};

Тестирование обработчиков

Тестирование серверных функций в Fresh не отличается от обычного тестирования функций Node/Deno. Основные подходы включают:

  • Юнит-тесты: проверка отдельной логики без запуска сервера.
  • Интеграционные тесты: эмуляция HTTP-запросов к обработчикам.

Юнит-тестирование

Юнит-тесты проверяют обработчики на корректную обработку входных данных и формирование ответа:

import { handler } from "./myHandler.js";
import { assertEquals } from "std/testing/asserts.ts";

Deno.test("GET возвращает корректный текст", async () => {
  const response = await handler.GET(new Request("http://localhost/"));
  const text = await response.text();
  assertEquals(text, "Hello, Fresh!");
});

В этом примере создаётся объект Request, аналогичный стандартному HTTP-запросу, и проверяется, что обработчик возвращает ожидаемый текст.

Интеграционное тестирование

Интеграционные тесты эмулируют полный поток запроса и ответа:

import { superoak } from "superoak";
import app from "../main.js"; // приложение Fresh

Deno.test("POST форма сохраняет email и делает редирект", async () => {
  const request = await superoak(app);
  await request.post("/subscribe")
    .send({ email: "user@example.com" })
    .expect(302)
    .expect("Location", "/thank-you");
});

Ключевое преимущество интеграционных тестов — проверка взаимодействия всех компонентов: маршрутов, обработчиков и сторонних сервисов (например, базы данных).


Практические советы

  • Разделение логики: обработчики должны оставаться тонкими, перенаправляя сложную бизнес-логику в отдельные функции или модули.
  • Обработка ошибок: всегда использовать try/catch внутри обработчиков или глобальные middleware для управления ошибками.
  • Тесты на валидность данных: проверка формы и запросов на стороне сервера предотвращает уязвимости и некорректные состояния.
  • Использование контекста: ctx позволяет передавать данные между middleware, обработчиками и компонентами, упрощая управление состоянием.

Взаимодействие с компонентами Fresh

Обработчики тесно связаны с компонентами Fresh:

  • SSR-компоненты получают данные через ctx.render(data), что делает серверный рендеринг полностью реактивным.
  • Hydration: минимальное использование JS на клиенте позволяет подключать только интерактивные компоненты без полного бандлинга.

Пример интеграции обработчика с компонентом:

export const handler = {
  GET(req, ctx) {
    const data = fetchData();
    return ctx.render({ items: data });
  }
};

В компоненте можно использовать полученные данные напрямую:

export default function Page({ data }) {
  return (
    <ul>
      {data.items.map(item => <li>{item.name}</li>)}
    </ul>
  );
}

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