mcpField
E
encore-testing

encore-testing

github.com/encoredev测试与质量30801 次安装2 天前更新
代码仓库

关于 encore-testing

encore-testing 面向使用 Vitest 测试 Encore.ts 应用的开发者,说明如何借助 Encore 测试基础设施,并覆盖端点、数据库、服务调用、身份验证、Pub/Sub、定时任务和外部依赖。

主要功能

  1. 01Vitest 配置与测试运行
  2. 02Encore 管理的测试基础设施
  3. 03端点和数据库测试
  4. 04服务调用与身份验证测试
  5. 05Pub/Sub 和定时函数测试
  6. 06模拟外部依赖

适用场景

  1. 01通过直接调用函数测试端点
  2. 02在隔离测试中验证数据库操作
  3. 03检查需身份验证的端点行为
  4. 04测试 Pub/Sub 事件和定时任务函数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/encoredev/skills --skill encore-testing

安装后在 Claude Code 中输入/encore-testing确认已加载。

详细介绍

测试 Encore.ts 应用

说明

Encore.ts 使用标准的 TypeScript 测试工具。推荐使用 Vitest。

设置 Vitest
npm install -D vitest

添加到 package.json:

{
  "scripts": {
    "test": "vitest"
  }
}
测试 API 端点
// api.test.ts
import { describe, it, expect } from "vitest";
import { hello } from "./api";

describe("hello endpoint", () => {
  it("returns a greeting", async () => {
    const response = await hello();
    expect(response.message).toBe("Hello, World!");
  });
});
运行测试
# Run with Encore (recommended - sets up infrastructure)
encore test

# Or run directly with npm
npm test

建议使用 encore test,因为它可以:

  • 自动设置测试数据库
  • 为每项测试提供隔离的基础设施
  • 处理服务依赖项
使用请求参数进行测试
// api.test.ts
import { describe, it, expect } from "vitest";
import { getUser } from "./api";

describe("getUser endpoint", () => {
  it("returns the user by ID", async () => {
    const user = await getUser({ id: "123" });
    expect(user.id).toBe("123");
    expect(user.name).toBeDefined();
  });
});
测试数据库操作

Encore 会提供隔离的测试数据库:

// user.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { createUser, getUser, db } from "./user";

describe("user operations", () => {
  beforeEach(async () => {
    // Clean up before each test
    await db.exec`DELETE FROM users`;
  });

  it("creates and retrieves a user", async () => {
    const created = await createUser({ email: "[email protected]", name: "Test" });
    const retrieved = await getUser({ id: created.id });
    
    expect(retrieved.email).toBe("[email protected]");
  });
});
测试服务间调用
// order.test.ts
import { describe, it, expect } from "vitest";
import { createOrder } from "./order";

describe("order service", () => {
  it("creates an order and notifies user service", async () => {
    // Service calls work normally in tests
    const order = await createOrder({
      userId: "user-123",
      items: [{ productId: "prod-1", quantity: 2 }],
    });
    
    expect(order.id).toBeDefined();
    expect(order.status).toBe("pending");
  });
});
测试错误情况
import { describe, it, expect } from "vitest";
import { getUser } from "./api";
import { APIError } from "encore.dev/api";

describe("error handling", () => {
  it("throws NotFound for missing user", async () => {
    await expect(getUser({ id: "nonexistent" }))
      .rejects
      .toThrow("user not found");
  });

  it("throws with correct error code", async () => {
    try {
      await getUser({ id: "nonexistent" });
    } catch (error) {
      expect(error).toBeInstanceOf(APIError);
      expect((error as APIError).code).toBe("not_found");
    }
  });
});
测试经过身份验证的端点

模拟 getAuthData,以指定用户身份直接调用 auth: true 端点:

import { describe, it, expect, vi } from "vitest";
import * as auth from "~encore/auth";
import { getProfile } from "./api";

it("returns the caller's profile", async () => {
  vi.spyOn(auth, "getAuthData").mockImplementation(() => ({ userID: "123" }));
  const profile = await getProfile();
  expect(profile.id).toBe("123");
});

此模拟仅影响测试进程中直接读取 getAuthData() 的代码。通过 ~encore/clients 发起的调用会经过 Encore 运行时,而运行时无法看到此模拟,因此以这种方式调用 auth: true 端点会报错:“endpoint requires auth but none provided”。请改为显式传入身份验证数据:

import { user } from "~encore/clients";

await user.follow({ username: "jake" }, { authData: { userID: "123" } });
测试 Pub/Sub
// notifications.test.ts
import { describe, it, expect, vi } from "vitest";
import { orderCreated } from "./events";

describe("pub/sub", () => {
  it("publishes order created event", async () => {
    const messageId = await orderCreated.publish({
      orderId: "order-123",
      userId: "user-456",
      total: 9999,
    });
    
    expect(messageId).toBeDefined();
  });
});
测试 Cron 作业

测试底层函数,而不是 cron 计划:

// cleanup.test.ts
import { describe, it, expect } from "vitest";
import { cleanupExpiredSessions } from "./cleanup";

describe("cleanup job", () => {
  it("removes expired sessions", async () => {
    // Create some expired sessions first
    await createExpiredSession();
    
    // Call the endpoint directly
    await cleanupExpiredSessions();
    
    // Verify cleanup happened
    const remaining = await countSessions();
    expect(remaining).toBe(0);
  });
});
模拟外部服务
import { describe, it, expect, vi, beforeEach } from "vitest";
import { sendWelcomeEmail } from "./email";

// Mock external API
vi.mock("./external-email-client", () => ({
  send: vi.fn().mockResolvedValue({ success: true }),
}));

describe("email service", () => {
  it("sends welcome email", async () => {
    const result = await sendWelcomeEmail({ userId: "123" });
    expect(result.sent).toBe(true);
  });
});
测试配置

创建 vite.config.ts(使用 ~encore 导入时必需):

import { defineConfig } from "vitest/config";
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "~encore": path.resolve(__dirname, "./encore.gen"),
    },
  },
  test: {
    globals: true,
    environment: "node",
    include: ["**/*.test.ts"],
    coverage: {
      reporter: ["text", "json", "html"],
    },
  },
});
VS Code 集成

安装 Vitest 扩展,并添加到 .vscode/settings.json:

{
  "vitest.commandLine": "encore test"
}

**注意:**对于 VS Code 测试资源管理器,请禁用文件级并行,以避免端口冲突:

// vite.config.ts
export default defineConfig({
  // ...
  test: {
    fileParallelism: false,  // Disable for VS Code
    // ...
  },
});

在 CI 中重新启用:encore test --fileParallelism=true

指南
  • 使用 encore test 运行测试并设置基础设施
  • 每个测试文件都会获得独立的数据库事务(完成后回滚)
  • 直接将 API 端点作为函数调用来测试
  • 服务间调用在测试中可以正常工作
  • 模拟外部依赖项(第三方 API、电子邮件服务等)
  • 不要模拟 Encore 基础设施(数据库、Pub/Sub),请使用实际服务

评论

支持 Markdown