Testing JSON APIs with Vitest: Mocking, Snapshots, and Assertions
The Dark Side of Un-tested APIs
We've all been there - a new feature is shipped, and the API is assumed to be working as expected. But, as the first users start interacting with it, the error reports start pouring in. The culprit? Un-tested JSON APIs. In this post, we'll explore how to test JSON APIs with Vitest, a fast and efficient testing framework.
Table of Contents
- Setting up Vitest for API Testing
- Mocking API Requests with MSW
- Snapshots and Custom Matchers
- Parallel Test Runs for Faster Feedback
- Real-World Example: Testing a Todo API
- Key Takeaways
- FAQ
Setting up Vitest for API Testing
Before we dive into the nitty-gritty, let's set up Vitest for API testing. We'll assume you have a basic understanding of Vitest and its configuration. If not, don't worry! We'll provide a brief overview.
First, install Vitest and its dependencies:
npm install vitest @vitest/core --save-dev
Next, create a vitest.config.js file with the following configuration:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
},
});
This sets up Vitest to run in a jsdom environment, which allows us to test our API in a simulated browser environment.
Mocking API Requests with MSW
When testing APIs, we often need to mock out the actual API requests to isolate our tests and make them more reliable. We'll use MSW (Mock Service Worker) to achieve this.
First, install MSW:
npm install msw --save-dev
Next, create a src/mocks/handlers.js file with the following code:
import { rest } from 'msw';
export const handlers = [
rest.get('/api/todos', (req, res, ctx) => {
return res(ctx.json([{ id: 1, title: 'Todo 1' }]));
}),
];
This sets up a mock API endpoint that returns a JSON response with a single todo item.
Snapshots and Custom Matchers
Vitest provides a powerful feature called snapshots, which allows us to assert that the API response matches a pre-defined snapshot. We'll use this feature to test our API.
First, create a src/tests/todos.test.js file with the following code:
import { test, expect } from 'vitest';
import { handlers } from '../mocks/handlers';
test('GET /api/todos returns a list of todos', async () => {
const response = await fetch('/api/todos');
expect(response.ok).toBe(true);
expect(await response.json()).toMatchSnapshot();
});
This test uses the toMatchSnapshot matcher to assert that the API response matches the pre-defined snapshot.
Parallel Test Runs for Faster Feedback
One of the benefits of Vitest is its ability to run tests in parallel, which significantly speeds up the testing process. We can take advantage of this feature by using the test.concurrent function.
Update the todos.test.js file with the following code:
import { test, expect } from 'vitest';
import { handlers } from '../mocks/handlers';
test.concurrent('GET /api/todos returns a list of todos', async () => {
const response = await fetch('/api/todos');
expect(response.ok).toBe(true);
expect(await response.json()).toMatchSnapshot();
});
This test will now run in parallel with other tests, providing faster feedback.
Real-World Example: Testing a Todo API
Let's put everything together with a real-world example. We'll test a Todo API that allows users to create, read, update, and delete (CRUD) todo items.
Create a src/api/todos.js file with the following code:
import fetch from 'node-fetch';
const api = {
async getTodos() {
const response = await fetch('/api/todos');
return response.json();
},
async createTodo(title) {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
});
return response.json();
},
async updateTodo(id, title) {
const response = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
});
return response.json();
},
async deleteTodo(id) {
const response = await fetch(`/api/todos/${id}`, { method: 'DELETE' });
return response.json();
},
};
export default api;
This API provides basic CRUD operations for todo items.
Create a src/tests/todos.test.js file with the following code:
import { test, expect } from 'vitest';
import api from '../api/todos';
import { handlers } from '../mocks/handlers';
test.concurrent('GET /api/todos returns a list of todos', async () => {
const response = await api.getTodos();
expect(response).toMatchSnapshot();
});
test.concurrent('POST /api/todos creates a new todo', async () => {
const response = await api.createTodo('New Todo');
expect(response).toMatchSnapshot();
});
test.concurrent('PATCH /api/todos updates a todo', async () => {
const response = await api.updateTodo(1, 'Updated Todo');
expect(response).toMatchSnapshot();
});
test.concurrent('DELETE /api/todos deletes a todo', async () => {
const response = await api.deleteTodo(1);
expect(response).toMatchSnapshot();
});
This test suite covers all CRUD operations for todo items.
Key Takeaways
- Use Vitest to test JSON APIs for faster and more efficient testing.
- Mock API requests with MSW to isolate tests and make them more reliable.
- Use snapshots and custom matchers to assert API responses.
- Run tests in parallel with
test.concurrentfor faster feedback.
FAQ
Q: What is Vitest?
A: Vitest is a fast and efficient testing framework for JavaScript.
Q: What is MSW?
A: MSW (Mock Service Worker) is a library that allows us to mock API requests.
Q: What are snapshots?
A: Snapshots are a feature in Vitest that allows us to assert that the API response matches a pre-defined snapshot.