Agent SkillsUsefulSoftwareCo/executor › effect-http-testing

effect-http-testing

GitHub

提供基于 Effect 平台的 HTTP API 端到端测试指南。涵盖通过 HttpApiBuilder 定义 API、实现处理器,并利用 NodeHttpServer.layerTest 构建内存测试服务器与 HttpClient 注入的核心模式,确保层组合正确及路径参数处理无误。

.skills/effect-http-testing/SKILL.md UsefulSoftwareCo/executor

触发场景

编写涉及 @effect/platform 的 HTTP API 测试 配置 HttpApiBuilder 或 HttpClient 测试环境 解决 Effect HTTP 服务集成测试中的层依赖问题

安装

npx skills add UsefulSoftwareCo/executor --skill effect-http-testing -g -y
更多选项

非标准路径

npx skills add https://github.com/UsefulSoftwareCo/executor/tree/main/.skills/effect-http-testing -g -y

不安装直接使用

npx skills use UsefulSoftwareCo/executor@effect-http-testing

指定 Agent (Claude Code)

npx skills add UsefulSoftwareCo/executor --skill effect-http-testing -a claude-code -g -y

安装 repo 全部 skill

npx skills add UsefulSoftwareCo/executor --all -g -y

预览 repo 内 skill

npx skills add UsefulSoftwareCo/executor --list

SKILL.md

Frontmatter
{
    "name": "effect-http-testing",
    "description": "Testing Effect HttpApi services end-to-end. Use when writing tests that involve Effect's HttpApi, HttpApiBuilder, HttpClient, HttpServer, or when testing any HTTP service\/plugin built with @effect\/platform. Covers proper layer composition, test server setup, HttpClient injection, and common pitfalls."
}

Effect HTTP Testing

Core Pattern

Define an API with HttpApi, implement handlers with HttpApiBuilder.group, serve it with HttpApiBuilder.serve(), and use NodeHttpServer.layerTest to get an in-process test server + HttpClient pointed at it.

import { expect, layer } from "@effect/vitest";
import { Effect, Layer, Schema } from "effect";
import {
  HttpApi,
  HttpApiBuilder,
  HttpApiEndpoint,
  HttpApiGroup,
  HttpClient,
  OpenApi,
} from "@effect/platform";
import { NodeHttpServer } from "@effect/platform-node";

// 1. Define the API
class Item extends Schema.Class<Item>("Item")({
  id: Schema.Number,
  name: Schema.String,
}) {}

const ItemsGroup = HttpApiGroup.make("items")
  .add(HttpApiEndpoint.get("listItems", "/items").addSuccess(Schema.Array(Item)))
  .add(
    HttpApiEndpoint.get("getItem", "/items/:itemId")
      .setPath(Schema.Struct({ itemId: Schema.NumberFromString }))
      .addSuccess(Item),
  );

const MyApi = HttpApi.make("myApi").add(ItemsGroup);

// 2. Implement handlers
const ItemsLive = HttpApiBuilder.group(MyApi, "items", (handlers) =>
  handlers
    .handle("listItems", () => Effect.succeed([{ id: 1, name: "Widget" }]))
    .handle("getItem", (req) => Effect.succeed({ id: req.path.itemId, name: "Widget" })),
);

// 3. Build test layer
const ApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(ItemsLive));

const TestLayer = HttpApiBuilder.serve().pipe(
  Layer.provide(ApiLive),
  Layer.provideMerge(NodeHttpServer.layerTest),
);

// 4. Use layer() to share across tests
layer(TestLayer)("My API", (it) => {
  it.effect("works", () =>
    Effect.gen(function* () {
      const client = yield* HttpClient.HttpClient;
      // client is already pointed at the test server
      const response = yield* client.get("/items");
      // ...
    }),
  );
});

Critical Rules

Layer composition order matters

HttpApiBuilder.serve() consumes HttpApi.Api. The API layer must be provided to it, not the other way around:

// CORRECT
HttpApiBuilder.serve().pipe(Layer.provide(ApiLive), Layer.provideMerge(NodeHttpServer.layerTest));

// WRONG — "Service not found: HttpApi.Api"
ApiLive.pipe(Layer.provide(HttpApiBuilder.serve()), Layer.provideMerge(NodeHttpServer.layerTest));

layerTestClient prepends the server URL

NodeHttpServer.layerTest (and HttpServer.layerTestClient) produce an HttpClient that automatically prepends the test server's http://127.0.0.1:<port> to every request URL.

  • Use paths only (/items, /items/2) in requests — not full URLs
  • If your code builds full URLs (e.g. http://localhost/items), the client will produce http://127.0.0.1:PORThttp://localhost/items — an invalid URL
  • When injecting the test client into code that normally uses a baseUrl, pass baseUrl: "" or skip the base URL entirely

Path parameters need setPath()

Effect's HttpApiEndpoint with :param syntax does NOT automatically populate req.path. You must call .setPath() with a schema:

// WRONG — req.path is undefined
HttpApiEndpoint.get("getItem", "/items/:itemId").addSuccess(Item);

// CORRECT — req.path.itemId is typed and populated
HttpApiEndpoint.get("getItem", "/items/:itemId")
  .setPath(Schema.Struct({ itemId: Schema.NumberFromString }))
  .addSuccess(Item);

OpenApi.fromApi generates the spec

Use OpenApi.fromApi(api) to generate an OpenAPI spec from an HttpApi definition. The generated spec:

  • Uses "Api" as the default title (not the api id)
  • Converts :param to {param} in paths
  • Does NOT list path parameters in the parameters array — they're implicit in the path template
  • Uses group.endpoint format for operationIds (e.g. items.listItems)

Grab the test HttpClient from context

Inside layer() tests, the HttpClient is available in the Effect context:

layer(TestLayer)("tests", (it) => {
  it.effect("test", () =>
    Effect.gen(function* () {
      const httpClient = yield* HttpClient.HttpClient;
      // Use it directly or wrap in a Layer for injection
      const clientLayer = Layer.succeed(HttpClient.HttpClient, httpClient);
    }),
  );
});

Use HttpClient for HTTP calls, not fetch

Production code should use HttpClient from @effect/platform, not raw fetch:

import { HttpClient, HttpClientRequest } from "@effect/platform";

// Build request
let request = HttpClientRequest.get("/items");
request = HttpClientRequest.setHeader(request, "accept", "application/json");
request = HttpClientRequest.setUrlParam(request, "limit", "10");

// Execute — requires HttpClient in context
const response = yield * client.execute(request);

// Read body
const data = yield * response.json; // Effect<unknown>
const text = yield * response.text; // Effect<string>

This makes testing clean — swap in a test client layer, no monkey-patching needed.

Response headers are Record<string, string>

Effect's HttpClientResponse.headers is a plain Record<string, string>, not a Web Headers object. Don't call .forEach() or .get() on it:

// WRONG
response.headers.forEach((v, k) => ...)
response.headers.get("content-type")

// CORRECT
const ct = response.headers["content-type"]
const copy = { ...response.headers }

HttpClientRequest.make takes uppercase methods

// The method parameter to make() must be uppercase
HttpClientRequest.make("GET")("/items");

// Or use the convenience methods
HttpClientRequest.get("/items");
HttpClientRequest.post("/items");

Prepending base URLs to a client

Use HttpClient.mapRequest with HttpClientRequest.prependUrl:

const clientWithBase = Layer.effect(
  HttpClient.HttpClient,
  Effect.map(
    HttpClient.HttpClient,
    HttpClient.mapRequest(HttpClientRequest.prependUrl("https://api.example.com")),
  ),
).pipe(Layer.provide(baseClientLayer));

Error assertions

Use Effect.flip to turn errors into values for assertion:

const error = yield * Effect.flip(someFailingEffect);
expect(error._tag).toBe("MyError");

Dependencies

  • @effect/platform — HttpApi, HttpClient, HttpServer, OpenApi
  • @effect/platform-node — NodeHttpServer.layerTest (for Node/vitest)
  • @effect/vitestlayer(), it.effect(), expect

版本历史

  • fd4fb02 当前 2026-07-05 10:55

同 Skill 集合

.agents/skills/stack/SKILL.md
.agents/skills/wrdn-effect-atom-optimistic/SKILL.md
.agents/skills/wrdn-effect-atom-reactivity-keys/SKILL.md
.agents/skills/wrdn-effect-promise-exit/SKILL.md
.agents/skills/wrdn-effect-raw-fetch-boundary/SKILL.md
.agents/skills/wrdn-effect-schema-boundaries/SKILL.md
.agents/skills/wrdn-effect-schema-inferred-types/SKILL.md
.agents/skills/wrdn-effect-typed-errors/SKILL.md
.agents/skills/wrdn-effect-value-inferred-types/SKILL.md
.agents/skills/wrdn-effect-vitest-tests/SKILL.md
.agents/skills/wrdn-package-boundaries/SKILL.md
.agents/skills/wrdn-typescript-type-safety/SKILL.md
.claude/skills/emulate/SKILL.md
.claude/skills/prod-telemetry/SKILL.md
.claude/skills/self-contained-modals/SKILL.md
.skills/cli-release/SKILL.md
.skills/effect-atom-optimistic-updates/SKILL.md
.skills/effect-use-pattern/SKILL.md
.skills/graphite/SKILL.md
.skills/warden-security-review/SKILL.md

元信息

文件数
0
版本
8bc037d
Hash
a0ab3102
收录时间
2026-07-05 10:55

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-18 01:55
浙ICP备14020137号-1 $访客地图$