Steve Kinney

Full-Stack TypeScript

Testing Express APIs with Zod Fixtures and Superagent

Testing your Express API is crucial for ensuring its reliability and correctness. This guide demonstrates how to use zod-fixture to generate realistic test data based on your Zod schemas, and superagent to make HTTP requests to your API.

Install Dependencies

Install the necessary dependencies:

npm install superagent zod zod-fixture --save-dev
npm install @types/superagent --save-dev # if using TypeScript

This installs:

  • superagent: HTTP client for testing your API
  • zod: Schema validation library
  • zod-fixture: Generates test data from Zod schemas

Define Your Zod Schemas

Ensure that your Express API uses Zod schemas for request and response validation.

Example:

// schemas.ts
import { z } from 'zod';

export const createUserSchema = z.object({
	username: z.string().min(3),
	email: z.string().email(),
	age: z.number().int().positive().optional(),
});

export const userResponseSchema = z.object({
	id: z.string().uuid(),
	username: z.string(),
	email: z.string(),
	age: z.number().int().positive().optional(),
});

Create Zod Fixtures

Use zod-fixture to generate test data based on your Zod schemas.

Example:

// fixtures.ts
import { generateMock } from 'zod-fixture';
import { createUserSchema, userResponseSchema } from './schemas';

export const createUserFixture = () => generateMock(createUserSchema);
export const userResponseFixture = () => generateMock(userResponseSchema);

Write Test Cases

Use superagent to make HTTP requests to your Express API and zod-fixture to generate test data.

Example (using Jest):

// app.test.ts
import request from 'superagent';
import { createUserFixture, userResponseFixture } from './fixtures';
import { createUserSchema, userResponseSchema } from './schemas';
import express, { Request, Response } from 'express';
import { z } from 'zod';

const app = express();
app.use(express.json());

app.post(
	'/users',
	(
		req: Request<{}, {}, z.infer<typeof createUserSchema>>,
		res: Response<z.infer<typeof userResponseSchema>>,
	) => {
		const newUser: z.infer<typeof userResponseSchema> = {
			id: '123e4567-e89b-12d3-a456-426614174000',
			username: req.body.username,
			email: req.body.email,
			age: req.body.age,
		};
		res.status(201).json(newUser);
	},
);

const server = app.listen(3001); // Use a different port for testing

describe('User API', () => {
	afterAll(() => {
		server.close();
	});

	it('should create a user', async () => {
		const userData = createUserFixture();
		const response = await request.post('http://localhost:3001/users').send(userData);

		expect(response.status).toBe(201);
		expect(response.body).toEqual(expect.objectContaining(userData));
		expect(userResponseSchema.safeParse(response.body).success).toBe(true);
	});

	it('should handle invalid input', async () => {
		const invalidUserData = { username: 'a', email: 'invalid-email' }; // Invalid username and email
		try {
			await request.post('http://localhost:3001/users').send(invalidUserData);
		} catch (error: any) {
			expect(error.response.status).toBe(400); // Assuming your API returns 400 for invalid input
		}
	});

	it('should generate a valid user response fixture', () => {
		const responseData = userResponseFixture();
		expect(userResponseSchema.safeParse(responseData).success).toBe(true);
	});
});

Run Tests

Run your tests using your chosen testing framework (e.g., Jest, Mocha).

npx jest app.test.ts

Benefits

  • Realistic Test Data: zod-fixture generates realistic test data based on your Zod schemas, ensuring that your tests closely resemble real-world scenarios.
  • Type Safety: Zod schemas and fixtures provide type safety, reducing the risk of errors.
  • Simplified Testing: superagent simplifies the process of making HTTP requests, making your tests more readable and maintainable.
  • Validation: You can easily validate the response body against your zod schemas.
  • Improved Reliability: Comprehensive testing improves the reliability of your Express API.

Last modified on .