This example demonstrates how to write unit tests for Zuplo handlers by mocking ZuploRequest and ZuploContext. The sample uses Mocha and Sinon for testing and mocking, but the concepts apply to any test framework.
- A Zuplo account. You can sign up for free.
- Node.js installed locally for running tests
Working locally is the best way to explore and understand the code for this example. You can get a local version by using the Zuplo CLI:
npx create-zuplo-api@latest --example test-mocks
Then, in the project directory run the following commands:
To run the unit tests:
It is also possible to deploy this example directly to your Zuplo account and work with it via the Zuplo Portal. You can do this by clicking the Deploy to Zuplo button anywhere on this page.
Since ZuploRequest extends the standard Request class, you can create a mock by extending Request and adding the Zuplo-specific properties:
class ZuploRequest extends Request {
params: Record<string, string>;
constructor(input: string | Request, init?: RequestInit & { params?: Record<string, string> }) {
super(input, init);
this.params = init?.params || {};
}
}
Mocking ZuploContext
Create a mock context object with the properties your handlers use:
export const context = {
log: {
info: () => {},
warn: () => {},
error: () => {},
},
route: {
pathPattern: "/test",
},
// Add other properties as needed
};
With the mocks in place, you can test handlers directly:
it("Calls the handler and validates the result", async function () {
const contextSpy = sandbox.spy(context.log, "info");
const mockRequest = new ZuploRequest("https://test.com", {
method: 'POST',
body: JSON.stringify({ hello: "world" })
});
const response = await handler1(mockRequest, context);
const result = await response.json();
assert(contextSpy.calledOnce);
assert(result.hello === "world");
});
Use undici's MockAgent to mock fetch calls your handlers make:
import { MockAgent, setGlobalDispatcher } from "undici";
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
const mockPool = mockAgent.get("https://echo.zuplo.io");
mockPool
.intercept({ path: "/hello" })
.reply(200, { data: "mocked response" });
├── config/
│ └── routes.oas.json # Route definitions
├── modules/
│ ├── handler1.ts # Handler that logs and returns body
│ └── handler2.ts # Handler that makes external API call
└── unit-tests/
├── mocks.ts # Mock implementations
└── handlers.spec.ts # Test file
it("Calls the handler and validates the result", async function () {
const mockRequest = new ZuploRequest("https://test.com", {
method: 'POST',
body: JSON.stringify({ hello: "world" })
});
const response = await handler1(mockRequest, context);
const result = await response.json();
assert(result.hello === "world");
});
it("Handles path parameters", async function () {
const mockRequest = new ZuploRequest("https://my-api.zuplo.app", {
params: { param1: "hello" },
});
const result = await handler2(mockRequest, context);
assert(result.data === "expected value");
});
it("Verifies logging was called", async function () {
const logSpy = sandbox.spy(context.log, "info");
await handler1(mockRequest, context);
assert(logSpy.calledOnce);
assert(logSpy.calledWith({ hello: "world" }));
});
- Add more mock properties: Extend the mock context with
request.user, context.custom, etc.
- Test policies: Apply the same mocking approach to test custom policies
- Test error cases: Verify handlers return appropriate error responses
- Integration tests: Use Zuplo's test framework for end-to-end testing