Testing GET Endpoints
The GET testing pattern
Every GET test follows the same pattern: seed known data, make a request, assert the response matches expectations.
Testing a list endpoint
describe("GET /v2/books", () => {
beforeEach(() => {
testDb.exec("DELETE FROM reviews; DELETE FROM books; DELETE FROM authors;");
testDb.prepare("INSERT INTO authors (id, name) VALUES (?, ?)").run("a1", "Butler");
testDb.prepare("INSERT INTO authors (id, name) VALUES (?, ?)").run("a2", "Borges");
testDb
.prepare("INSERT INTO books (id, title, author_id, genre) VALUES (?, ?, ?, ?)")
.run("b1", "Kindred", "a1", "science-fiction");
testDb
.prepare("INSERT INTO books (id, title, author_id, genre) VALUES (?, ?, ?, ?)")
.run("b2", "Ficciones", "a2", "fiction");
});
test("returns all books", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books"));
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveLength(2);
});
test("books are ordered by title", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books"));
const data = await response.json();
expect(data[0].title).toBe("Ficciones");
expect(data[1].title).toBe("Kindred");
});
test("each book has the v2 response shape", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books"));
const data = await response.json();
const book = data[0];
expect(book.id).toBeDefined();
expect(book.title).toBeDefined();
expect(book.author).toBeDefined();
expect(book.author.id).toBeDefined();
expect(book.author.name).toBeDefined();
expect(book.genre).toBeDefined();
expect(book.createdAt).toBeDefined();
// v1 fields should NOT be present
expect(book.author_name).toBeUndefined();
expect(book.created_at).toBeUndefined();
});
}); Testing query parameters
describe("GET /v2/books with query params", () => {
test("filters by genre", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books?genre=fiction"));
const data = await response.json();
expect(data).toHaveLength(1);
expect(data[0].genre).toBe("fiction");
});
test("returns empty array for genre with no books", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books?genre=fantasy"));
const data = await response.json();
expect(data).toHaveLength(0);
});
}); Testing a single-resource endpoint
describe("GET /v2/books/:id", () => {
test("returns the book with correct shape", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books/b1"));
const book = await response.json();
expect(response.status).toBe(200);
expect(book.id).toBe("b1");
expect(book.title).toBe("Kindred");
expect(book.author).toEqual({ id: "a1", name: "Butler" });
});
test("returns 404 for nonexistent book", async () => {
const response = await app.fetch(new Request("http://localhost/v2/books/nonexistent"));
expect(response.status).toBe(404);
});
test("includes ratings", async () => {
testDb
.prepare("INSERT INTO reviews (id, book_id, user_id, rating) VALUES (?, ?, ?, ?)")
.run("r1", "b1", "u1", 5);
testDb
.prepare("INSERT INTO reviews (id, book_id, user_id, rating) VALUES (?, ?, ?, ?)")
.run("r2", "b1", "u2", 3);
const response = await app.fetch(new Request("http://localhost/v2/books/b1"));
const book = await response.json();
expect(book.ratings.average).toBe(4);
expect(book.ratings.count).toBe(2);
});
}); Notice how the ratings test seeds reviews in the test itself — not in beforeEach. This keeps each test self-contained: the rating test adds reviews because it is testing ratings. Other tests do not need reviews.
What to assert
Status code. Always check it first. A 500 with the right body shape is still a bug.
Response shape. Check that expected fields exist and unexpected fields do not (v1 fields on v2 endpoints).
Data correctness. Check that values match what was seeded. Title is “Kindred”, not “Ficciones”.
Edge cases. Empty results, not found, no reviews (null rating).
Exercises
Exercise 1: Write tests for a list endpoint. Seed 3 items. Check count, order, and response shape.
Exercise 2: Write tests for a detail endpoint. Test found (200), not found (404), and with related data (reviews/ratings).
Exercise 3: Test query parameter filtering. Seed books in multiple genres. Verify filtering returns only matching books.
Why seed data in beforeEach instead of relying on data from previous tests?