In this lesson, you will learn how to detect N+1 performance bottlenecks in your GraphQL resolvers by asserting the exact number of database calls triggered by a single query.
The N+1 Problem in GraphQL
The N+1 problem occurs when a resolver fetches a parent list (e.g., 10 users) and then executes a secondary query for every individual item to fetch related data (e.g., 10 separate queries for each user's posts). This results in 1 + N database hits, which cripples performance as your data grows.
Verifying Fetching Patterns
To test this, we don't just check the JSON output; we intercept the data-fetching layer (the repository or service layer) to count the actual calls made to the database. We can use a spy or a mock function on our data access layer to assert execution counts.
Consider a query fetching 3 authors and their books. A naive implementation would trigger 4 database calls (1 for authors, 3 for books). A "batch-optimized" implementation should only trigger 2 (1 for authors, 1 for all relevant books via a DataLoader).
it('should fetch all books in exactly two database calls', async () => {
const findManySpy = jest.spyOn(db.books, 'findMany');
const findUsersSpy = jest.spyOn(db.users, 'findMany');
await executeQuery(`
query {
users {
name
books { title }
}
}
`);
// Asserting N+1 is solved: 1 call for users, 1 for all books
expect(findUsersSpy).toHaveBeenCalledTimes(1);
expect(findManySpy).toHaveBeenCalledTimes(1);
});
Why This Matters for Testing
If you rely solely on integration tests that check if data is returned, your tests will pass even if your resolver is inefficient. By spying on your database driver or repository methods, you transform your test suite into a performance guard. If a developer later introduces a loop inside a resolver that triggers individual lookups, your test will immediately fail, flagging the performance regression before it hits production.
Key Takeaway: To prevent N+1 issues, verify that your test suite asserts the total count of database operations, ensuring that batch-loading patterns remain intact as your schema evolves.