How to Unit Test an Express API in Node.js
Ever deployed an API only to have it break unexpectedly in production?
Few things are more frustrating—and costly—than bugs that slip through because your code wasn’t properly tested.
That’s where unit testing comes in.
In this guide, I’ll walk you through how to unit test an Express API in Node.js, using practical examples from a Task Manager project. You’ll learn what to test, how to test it, and how to build confidence in your code before it reaches production.
🚀 Complete JavaScript Guide (Beginner + Advanced)
🚀 NodeJS – The Complete Guide (MVC, REST APIs, GraphQL, Deno)
What Is Unit Testing?
Unit testing is a software testing technique where individual units of code—such as functions, methods, or classes—are tested in isolation.
These tests are usually:
- Automated
- Fast
- Focused on small pieces of logic
The goal is simple:
Verify that each unit behaves exactly as expected for a given input.
Why unit testing matters
- Catches bugs early
- Makes refactoring safer
- Improves long-term code reliability
- Gives you confidence to ship changes
Start With the Low-Hanging Fruit: Utility Functions
The best place to begin your unit testing journey is with utility functions.
Most utility functions are pure functions, meaning:
- Same input → same output
- No side effects
- No dependency on external state (DB, UI, environment, etc.)
Because of this, they’re incredibly easy to test.
Example: getErrorMessage
This function extracts a meaningful message from different kinds of input:
- An
Errorinstance - An object with a
messageproperty - A string
Test cases you should cover:
- Returns message from an Error object
- Returns message from an object with a
messageproperty - Returns message from a string
Using Jest, this looks like:
describe()to group teststest()for individual scenariosexpect()with matchers for assertions
If you’re new to Jest, spend time with the documentation—expect and matchers will become your daily tools.
Testing Functions With Dependencies
Not all functions are pure. Some rely on frameworks like Express.
For example, a utility like getPaginationParams() depends on the Express request object.
To test it properly, we simulate that dependency.
What to test
- Default pagination values when
pageandperPageare missing - Correct parsing when valid query parameters are provided
How to test it
- Create a mock request object
- Cast it to
Request(sometimes viaunknownfirst in TypeScript) - Pass it into your function
- Assert the returned pagination values
This lets you test real behavior without running an actual server.
Testing Classes and Business Logic
Next, move beyond functions and test classes—especially ones that contain business rules.
In the Task Manager project, we test a Task entity that handles:
- Completion logic
- Priority calculation
- DTO transformation
What to test in a class
1. Initialization
Make sure a new instance is created with the correct default values.
2. State changes
Example:
When calling markAsCompleted():
completedOnshould change fromnullto aDateinstance.
Use:
expect(task.completedOn).toBeInstanceOf(Date);
This is safer than checking an exact timestamp.
3. Error handling
If a task is already completed, calling markAsCompleted() again should throw an error.
Test that an error is thrown, not necessarily the exact message—this makes your tests more resilient to future changes.
4. Business rules
Test logic like:
- Priority is high when due date is within 1 day
- Priority is low when due date is a week away
- Priority is
nullwhen no due date exists
These are exactly the kinds of rules unit tests should protect.
Testing DTOs (Data Transfer Objects)
Your API responses are contracts with your users.
Unit tests help you ensure those contracts stay intact.
For example:
- Internal fields like
createdAtshould not appear in the response - Computed fields like
priorityLevelshould be included
Test this by:
- Creating a DTO from the entity
- Asserting excluded fields are missing
- Asserting required fields are present
Running Your Tests
Once your tests are in place:
npm run test
To generate a coverage report:
npm run test -- --coverage
⚠️ Don’t forget the two dashes — they pass the flag correctly to Jest.
How Much Coverage Is Enough?
Coverage is useful—but it’s not everything.
Some teams require:
- 70%
- 80%
- Or even 90% coverage
But remember:
High coverage doesn’t guarantee good tests.
A better rule of thumb
Test the code that contains:
- Business logic
- Decision-making
- State changes
If a piece of code has no logic, it may not need a test at all.
What’s Next?
So far, we’ve focused on:
- Pure functions
- Simple dependencies
- Entities and business logic
But real-world systems often include:
- External services
- Databases
- Complex module interactions
These require mocking, spies, and advanced testing patterns—which I cover in this article.
Final Thoughts
Unit testing isn’t just about avoiding bugs.
It’s about building confidence in your codebase.
When your APIs are backed by solid tests, you can:
- Refactor without fear
- Ship faster
- Sleep better at night
If you’re building Express APIs in Node.js, unit testing isn’t optional—it’s essential.