Skip to content

@jaypie/testkit

Prerequisites: npm install -D @jaypie/testkit vitest

@jaypie/testkit provides mock factories for all Jaypie packages and custom Vitest matchers for testing.

Terminal window
npm install -D @jaypie/testkit vitest
Export Purpose
matchers Vitest custom matchers
spyLog Spy on log methods
restoreLog Restore log methods
Mock factories Via @jaypie/testkit/mock
Category Matchers
Errors toThrowJaypieError, toThrowBadRequestError, toThrowNotFoundError, etc.
Types toBeClass, toBeMockFunction
Formats toMatchUuid, toMatchMongoId, toMatchJwt, toMatchBase64
Schema toMatchSchema
vitest.config.ts
export default defineConfig({
test: {
setupFiles: ["@jaypie/testkit/testSetup"],
},
});
import { expect } from "vitest";
import { matchers } from "@jaypie/testkit";
expect.extend(matchers);
import { vi } from "vitest";
vi.mock("jaypie", async () => {
const testkit = await import("@jaypie/testkit/mock");
return testkit;
});
import { expressHandler } from "jaypie";

The mock:

  • expressHandler executes the handler function directly
  • lambdaHandler executes the handler function directly
  • log methods are no-ops (or spies with spyLog)
  • Error classes work normally
vi.mock("jaypie", async () => {
const actual = await vi.importActual("jaypie");
const testkit = await import("@jaypie/testkit/mock");
return {
...actual,
expressHandler: testkit.expressHandler,
};
});
vi.mock("@jaypie/express", async () => {
const testkit = await import("@jaypie/testkit/mock");
return { expressHandler: testkit.expressHandler };
});
it("throws any Jaypie error", () => {
expect(() => fn()).toThrowJaypieError();
});
expect(() => fn()).toThrowBadRequestError();
expect(() => fn()).toThrowUnauthorizedError();
expect(() => fn()).toThrowForbiddenError();
expect(() => fn()).toThrowNotFoundError();
expect(() => fn()).toThrowInternalError();
expect(() => fn()).toThrowConfigurationError();
expect(() => fn()).toThrowBadGatewayError();
expect(() => fn()).toThrowUnavailableError();
await expect(asyncFn()).rejects.toThrowNotFoundError();
it("exports a class", () => {
expect(MyClass).toBeClass();
});
it("is a mock", () => {
const fn = vi.fn();
expect(fn).toBeMockFunction();
});
it("generates valid UUID", () => {
expect(uuid()).toMatchUuid();
});
it("is valid MongoDB ObjectId", () => {
expect("507f1f77bcf86cd799439011").toMatchMongoId();
});
it("is valid JWT format", () => {
expect(token).toMatchJwt();
});
it("is valid Base64", () => {
expect("SGVsbG8=").toMatchBase64();
});
import { jsonApiSchema } from "@jaypie/testkit";
it("returns JSON:API response", () => {
expect(response).toMatchSchema(jsonApiSchema);
});
import { log } from "jaypie";
import { spyLog, restoreLog } from "@jaypie/testkit";
beforeEach(() => {
spyLog(log);
});
afterEach(() => {
restoreLog(log);
});
it("logs trace on success", () => {
await handler();
expect(log.trace).toHaveBeenCalled();
});
it("does not log above trace", () => {
await handler();
expect(log.debug).not.toHaveBeenCalled();
expect(log.warn).not.toHaveBeenCalled();
expect(log.error).not.toHaveBeenCalled();
});
it("logs specific message", () => {
await handler();
expect(log.trace).toHaveBeenCalledWith("[handler] starting");
});

Seven-section structure:

describe("My Function", () => {
// 1. Base Cases
describe("Base Cases", () => {
it("is a function", () => {
expect(typeof myFn).toBe("function");
});
});
// 2. Error Conditions
describe("Error Conditions", () => {
it("throws on invalid input", () => {
expect(() => myFn(null)).toThrowBadRequestError();
});
});
// 3. Security
describe("Security", () => {
it("rejects unauthorized", () => {
expect(() => myFn({ auth: null })).toThrowUnauthorizedError();
});
});
// 4. Observability
describe("Observability", () => {
beforeEach(() => spyLog(log));
afterEach(() => restoreLog(log));
it("logs trace only on success", async () => {
await myFn();
expect(log.debug).not.toHaveBeenCalled();
});
});
// 5. Happy Paths
describe("Happy Paths", () => {
it("returns expected result", async () => {
const result = await myFn({ valid: true });
expect(result).toHaveProperty("success", true);
});
});
// 6. Features
describe("Features", () => {
it("supports pagination", async () => {
const result = await myFn({ page: 2 });
expect(result.page).toBe(2);
});
});
// 7. Specific Scenarios
describe("Specific Scenarios", () => {
it("handles empty results", async () => {
const result = await myFn({ filter: "none" });
expect(result.data).toEqual([]);
});
});
});
vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
setupFiles: ["@jaypie/testkit/testSetup"],
coverage: {
reporter: ["text", "html"],
},
},
});