Jest works just as well for backend Node.js code as it does for frontend components. This lesson covers testing patterns specific to Node.js: modules, environment variables, and the node test environment.
Testing Plain Node.js Modules
Backend Node.js code is often easier to test than UI code, since there's no DOM or rendering involved — you're usually testing pure functions, classes, or modules that read input and return output (or interact with mockable dependencies like a database or file system).
Set testEnvironment: 'node' (the default since Jest 28) for these projects, since there's no need for jsdom's overhead when nothing touches a simulated browser.
// slugify.js
function slugify(title) {
return title.toLowerCase().trim().replace(/\s+/g, '-');
}
module.exports = { slugify };
// slugify.test.js
const { slugify } = require('./slugify');
test('converts a title into a URL-friendly slug', () => {
expect(slugify('Hello World Article')).toBe('hello-world-article');
});
A pure Node.js utility function with no external dependencies is the simplest thing to test in any backend project.
Testing Code That Reads Environment Variables
test('uses the configured API base URL', () => {
process.env.API_BASE_URL = 'https://test.example.com';
const config = require('./config'); // re-require after setting env var
expect(config.apiBaseUrl).toBe('https://test.example.com');
});
Set process.env.VAR_NAME before requiring a module that reads it at load time.
Use jest.resetModules() if a module caches its config at import time and needs a fresh read.
Restore environment variables in afterEach() to avoid one test's env leaking into another.
Consider a dedicated .env.test file (with a library like dotenv) for consistent test configuration.
Jest + Node.js Cheat Sheet
Common backend testing setups and patterns.
Scenario
Approach
Pure utility function
Import and call directly, assert on the result
Reads process.env
Set env vars before requiring the module
Touches the file system
Mock fs (see Mocking Node Modules)
Class with dependencies
Inject mocked dependencies via the constructor
Test environment
testEnvironment: 'node' (default since Jest 28)
Testing Classes with Injected Dependencies
Backend classes often depend on other services (a logger, a repository, an email client). Designing classes to accept dependencies through their constructor (dependency injection) makes them dramatically easier to test, since you can pass in mock versions directly without needing jest.mock() at all.
class OrderService {
constructor(repository, emailClient) {
this.repository = repository;
this.emailClient = emailClient;
}
async placeOrder(order) {
await this.repository.save(order);
await this.emailClient.send(order.email, 'Order confirmed');
}
}
test('saves the order and sends a confirmation email', async () => {
const repository = { save: jest.fn() };
const emailClient = { send: jest.fn() };
const service = new OrderService(repository, emailClient);
await service.placeOrder({ email: 'ada@example.com' });
expect(repository.save).toHaveBeenCalled();
expect(emailClient.send).toHaveBeenCalledWith('ada@example.com', 'Order confirmed');
});
Resetting the Module Registry
jest.resetModules() clears Jest's internal module registry, forcing the next require() call to load a fresh copy of a module. This matters for modules that cache configuration or state at load time based on environment variables or singletons.
Importing a config module before setting the environment variables it depends on.
Hardcoding dependencies inside a class instead of injecting them, making mocking harder than necessary.
Forgetting to restore process.env after a test that modifies it.
Using testEnvironment: 'jsdom' for a pure backend project, adding unnecessary overhead.
Key Takeaways
Backend Node.js code is often simpler to test than UI code, with no DOM involved.
Dependency injection (passing dependencies via constructor/arguments) makes mocking straightforward.
Set environment variables before requiring modules that read them at load time.
jest.resetModules() forces a fresh module load, useful for config caching scenarios.
Pro Tip
Favor dependency injection over jest.mock() for your own classes and modules whenever practical — passing a mock object directly into a constructor is often simpler to read and maintain than mocking an entire imported module.
You now know how to test Node.js backend code. Next, learn how to test Express.js routes and middleware.