Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
10 Aug 2025 · 5 min read ·Article 41 / 125
Go

41 Introduction to Unit Testing for Resolvers

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

41 Introduction to Unit Testing for Resolvers

Unit testing has become a fundamental part of the modern software development lifecycle. It not only ensures that code behaves as expected, but also helps maintain code quality as the system grows and requirements change. In this 41st article of our introductory testing series, I’ll focus specifically on unit testing for resolvers—a component that is often overlooked in testing, especially in applications that use GraphQL or the resolver design pattern in traditional backends.

This article covers everything from what a resolver is, why a unit test matters, all the way to a complete implementation example with simulations, tables, and a flow diagram using mermaid.


What Is a Resolver?

Before we dive into unit testing, it’s important to understand what a resolver is.

Danger
A resolver is a function responsible for returning data for a given request, typically within the GraphQL architectural pattern. However, the term resolver can also apply to systems that map requests to dynamic handlers, such as in the service locator pattern and API gateways.

As an illustration, in GraphQL a resolver usually looks like this:

js
1const resolvers = {
2  Query: {
3    user: (parent, args, context) => {
4      // Logic to fetch the user based on args.id
5      return getUserById(args.id);
6    },
7  },
8};

Why Is Unit Testing Resolvers Important?

The following table summarizes why it’s important to unit test resolvers:

ReasonExplanation
Isolating business logicEnsures every function can run independently.
Catching errors earlierIdentifies bugs before the integration/deployment stage.
Preventing regressionsReduces the risk of errors when the code is updated.
Documenting edge casesEdge cases are documented through their tests.
Team/developer confidenceUnit tests become a reliable contract.

Standard Resolver Structure

Let’s look at the following resolver template, common in Node.js + GraphQL projects:

js
 1// resolvers/user.js
 2
 3const getUserById = require('../services/userService').getUserById;
 4
 5const userResolver = async (_, { id }, context) => {
 6  // Validate input
 7  if (!id) throw new Error('User ID is required');
 8  // Fetch the user data
 9  const user = await getUserById(id);
10  if (!user) throw new Error('User not found');
11  return user;
12};
13
14module.exports = userResolver;

In the resolver above, we need to ensure three things:

  1. The ID input must be present.
  2. The getUserById function is called and returns the correct result.
  3. An error is thrown if the user is not found.

Unit Testing a Resolver: Scenario Simulation

To perform unit testing, we must isolate external dependencies such as the database/external services using mocking.

Tools Used

  • Jest for the test runner and mocking.
  • Sinon (optional), if you need more advanced spies/stubs.

File Layout

text
1/services
2  userService.js
3/resolvers
4  user.js
5/tests
6  userResolver.test.js

Unit Test Example

js
 1// tests/userResolver.test.js
 2
 3const userResolver = require('../resolvers/user');
 4const userService = require('../services/userService');
 5
 6jest.mock('../services/userService');
 7
 8describe('User Resolver Unit Test', () => {
 9  afterEach(() => {
10    jest.clearAllMocks();
11  });
12
13  it('should throw error if id is missing', async () => {
14    await expect(userResolver(null, {}, {})).rejects.toThrow('User ID is required');
15  });
16
17  it('should call getUserById with correct id', async () => {
18    userService.getUserById.mockResolvedValue({ id: '123', name: 'Andy' });
19    const result = await userResolver(null, { id: '123' }, {});
20    expect(userService.getUserById).toBeCalledWith('123');
21    expect(result).toEqual({ id: '123', name: 'Andy' });
22  });
23
24  it('should throw error if user not found', async () => {
25    userService.getUserById.mockResolvedValue(null);
26    await expect(userResolver(null, { id: '321' }, {})).rejects.toThrow('User not found');
27  });
28});

Resolver and Testing Flow Diagram

To make things clearer, here is a simple flow of a resolver and its testing in a mermaid diagram:

MERMAID
flowchart TD
    A[Mulai Unit Test] --> B{ID Diberikan?}
    B -- Tidak --> C[Tolak dengan Error: ID is required]
    B -- Ya --> D[Panggil getUserById(id)]
    D --> E{User ditemukan?}
    E -- Tidak --> F[Tolak dengan Error: User not found]
    E -- Ya --> G[Kembalikan User]

Simulating the Unit Test Run

Let’s simulate the testing process in the following order:

  1. Test 1: No ID argument.
    • The resolver immediately validates and throws an error.
  2. Test 2: The ID argument is provided and the user is found.
    • The mocked service returns dummy data, and the resolver returns the result.
  3. Test 3: The ID argument is provided but the user is null.
    • The mock returns null, so the resolver throws an error.
No.ScenarioInputMock OutputExpected Result
1ID not provided{}-Error: ID is required
2User found{id: '123'}{id:'123', name}User Object
3User not found{id: '321'}nullError: User not found

Best Practices for Unit Testing Resolvers

  1. Test every path: Both the happy path and the error path.
  2. Mock external dependencies: Don’t let tests call the real service/database.
  3. Combine with coverage: Make sure critical paths are tested.
  4. Make tests readable: Test descriptions should be as clear as a specification.
  5. Don’t write integration tests here: A unit test only exercises a single unit/container.

Conclusion

Unit testing resolvers is often underrated, especially because a resolver feels like a simple function that’s “just a middleman.” Yet, as we’ve seen, unit testing resolvers is crucial. It guards the integration between domain logic, input validation, error handling, and the reliability of the response delivered to the frontend/API client.

By following best practices and leveraging tools like Jest, we can maintain code quality while making future changes more comfortable. I hope the explanations, examples, simulations, and tables in this 41st article give you enough insight to start testing resolvers in your own projects!


The writing and diagrams above are part of the Modern Testing Series on Medium. Don’t miss the next article covering more advanced testing techniques!

Related Articles

💬 Comments