Test API & Builder
The @glubean/sdk provides a flexible API for defining tests. You can write quick, single-function tests for simple endpoints, or use the Builder API to create complex, multi-step flows with typed state and cleanup logic.
Purpose
To accommodate both simple endpoint checks and complex end-to-end user journeys without forcing developers into rigid structures.
Design Rationale
- Two Modes: Simple tests need low boilerplate, while complex tests need structure. We provide a single
testfunction that scales: if you pass a callback, it’s a simple test. If you don’t, it returns aTestBuilderfor chaining steps. - Type-Safe State: In E2E tests, steps depend on data from previous steps (e.g., creating a user, then logging in). The Builder API passes state from step to step, fully typed, preventing “any” or “undefined” errors.
- Guaranteed Cleanup:
teardownis guaranteed to run even if steps fail or timeout. This ensures test data doesn’t pollute your environment.
Scenarios
Test Metadata & Tags
Instead of passing just a string ID to test(), you can pass a full metadata object. This is especially important for categorizing your tests with tags, giving them descriptions, or setting test-level timeouts.
import { test } from "@glubean/sdk";
// Quick Mode with Metadata
export const simpleTest = test({
id: "health-check",
name: "API Health",
tags: ["smoke", "p0"], // Filter test runs via CLI: glubean run --tag smoke
timeout: 10000,
}, async (ctx) => {
ctx.expect((await ctx.http.get("/health")).status).toBe(200);
});
// Builder Mode with Metadata
export const checkout = test({
id: "checkout-flow",
description: "Ensures a user can add items to cart and pay",
tags: ["e2e", "critical"],
skip: false,
only: false,
}).step(/* ... */);You can also apply metadata using the .meta() builder method if you prefer chaining:
export const checkout = test("checkout-flow")
.meta({ tags: ["e2e", "critical"], timeout: 60000 })
.step(/* ... */);Quick Mode (Single Function)
For simple endpoint checks, use the quick mode.
import { test } from "@glubean/sdk";
export const login = test("login", async (ctx) => {
const res = await ctx.http.post(`${ctx.vars.require("BASE_URL")}/login`, {
json: { username: "demo", password: "demo" }
});
ctx.expect(res.status).toBe(200);
});Builder Mode (Multi-Step with State)
When your test involves multiple dependent actions, use the Builder API.
interface TestState {
baseUrl: string;
cartId: string;
}
export const checkout = test<TestState>("checkout-flow")
.meta({ tags: ["e2e", "critical"] })
.setup(async (ctx) => {
// Setup runs first. It returns the initial state.
const baseUrl = ctx.vars.require("BASE_URL");
const cart = await ctx.http.post(`${baseUrl}/carts`).json<{ id: string }>();
return { baseUrl, cartId: cart.id };
})
.step("Add item", async (ctx, state) => {
// Each step receives the state from the previous step
await ctx.http.post(`${state.baseUrl}/carts/${state.cartId}/items`, {
json: { productId: "product-123" },
});
// Return updated state
return state;
})
.step("Complete checkout", async (ctx, state) => {
const order = await ctx.http.post(`${state.baseUrl}/carts/${state.cartId}/checkout`).json<{ status: string }>();
ctx.expect(order.status).toBe("completed");
})
.teardown(async (ctx, state) => {
// Teardown ALWAYS runs, even if a step failed.
await ctx.http.delete(`${state.baseUrl}/carts/${state.cartId}`);
});Focusing and Skipping
During local development, you often want to run just one test or skip flaky ones.
// Only this test will run when you execute `glubean run`
export const loginOnly = test.only("login-only", async (ctx) => {
// ...
});
// This test will be skipped
export const flakyFlow = test.skip("flaky-flow", async (ctx) => {
// ...
});Step Composition and Reusability
You can reuse sequences of steps using .use() or visually group them in reports using .group().
import type { TestBuilder } from "@glubean/sdk";
// Define a reusable authentication sequence
const withAuth = (b: TestBuilder<any>) =>
b
.step("login", async (ctx) => {
const data = await ctx.http.post("/auth/login").json<{ token: string }>();
return { token: data.token };
})
.step("verify token", async (ctx, { token }) => {
// ... verify ...
return { token };
});
export const checkout = test("checkout")
// .group works like .use, but creates a visual group in the test report
.group("auth", withAuth)
.step("pay", async (ctx, { token }) => {
// token is available here!
});