Jest is a JavaScript testing framework that allows you to easily mock and test environment variables. This is useful for testing how your application behaves under different environmental conditions, such as when running in development, staging, or production.
Here’s an example of how you can mock environment variables in Jest:
describe('environment variables', () => {
it('should mock process.env.NODE_ENV', () => {
// Mock the NODE_ENV environment variable
process.env.NODE_ENV = 'test';
// Assert that the environment variable is set correctly
expect(process.env.NODE_ENV).toBe('test');
});
it('should mock a custom environment variable', () => {
// Mock a custom environment variable
process.env.API_URL = 'http://localhost:3000/api';
// Assert that the environment variable is set correctly
expect(process.env.API_URL).toBe('http://localhost:3000/api');
});
});
In this example, we are using process.env to set and assert the values of environment variables. Jest runs tests in a Node.js environment, so you can directly manipulate process.env.
Best Practices for Mocking Environment Variables in Jest
-
Use
beforeEachorbeforeAll: To ensure a clean slate for each test or test suite, set your environment variables inbeforeEachorbeforeAllhooks. This prevents tests from interfering with each other.describe('environment variables with hooks', () => { beforeEach(() => { // Reset or set environment variables before each test process.env.NODE_ENV = 'test'; process.env.API_URL = 'http://localhost:3000/api'; }); afterEach(() => { // Clean up environment variables after each test delete process.env.NODE_ENV; delete process.env.API_URL; }); it('should have NODE_ENV set to test', () => { expect(process.env.NODE_ENV).toBe('test'); }); it('should have API_URL set correctly', () => { expect(process.env.API_URL).toBe('http://localhost:3000/api'); }); }); -
Clean Up After Tests: It’s crucial to clean up the environment variables you’ve set after your tests run to avoid polluting the global environment for other tests or processes. Use
afterEachorafterAllfor this.afterEach(() => { // Restore original values or delete the mocked variables delete process.env.NODE_ENV; delete process.env.API_URL; // If you need to restore original values, you'd store them before mocking. }); -
Use a Dedicated Library: For more complex scenarios or to manage a larger number of environment variables, consider using libraries like
dotenv-testorjest-runtime-env. These libraries can help load.envfiles specifically for testing and provide more structured ways to manage your test environment.npm install --save-dev dotenv-testCreate a
.env.testfile:NODE_ENV=test API_URL=http://test.api.com FEATURE_FLAG_X=trueIn your Jest setup file (e.g.,
jest.setup.js):// jest.setup.js require('dotenv-test').config();In
jest.config.js:module.exports = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], // ... other Jest configurations };Now,
process.env.NODE_ENV,process.env.API_URL, andprocess.env.FEATURE_FLAG_Xwill be available in your tests. -
Consider Global Setup: If you have environment variables that are common across all your tests, you can define them in Jest’s global setup. This is done via the
setupFilesAfterEnvoption in yourjest.config.js.// jest.config.js module.exports = { setupFilesAfterEnv: ['./jest.setup.js'], };// jest.setup.js process.env.DEFAULT_TIMEOUT = '5000';This ensures
process.env.DEFAULT_TIMEOUTis available in every test file.
Why Mocking Environment Variables is Important
Testing applications that rely on environment variables requires simulating different deployment scenarios. For instance, you might want to:
- Isolate Test Behavior: Ensure your tests run predictably by setting
NODE_ENVto'test'. This often disables certain behaviors or optimizations that are only relevant in production. - Test Configuration Logic: Verify that your application correctly reads and uses configuration values from environment variables. This is crucial for services that adapt their behavior based on the environment (e.g., API endpoints, database connection strings, feature flags).
- Simulate Different Environments: Test how your application behaves when connecting to different API endpoints (e.g., a staging API vs. a production API) or when feature flags are enabled or disabled.
The most surprising true thing about testing environment variables with Jest is how easily you can directly manipulate process.env. Unlike some other frameworks where you might need specific mocking utilities, Jest’s Node.js environment makes process.env a direct target for modification and assertion within your test files. This simplicity, however, is also where the pitfalls lie if you don’t manage the lifecycle of these variables carefully.
Consider a module that fetches data from an API, and the API URL is determined by an environment variable:
// src/apiService.js
const API_URL = process.env.API_URL || 'http://localhost:3001/api';
async function fetchData(endpoint) {
const response = await fetch(`${API_URL}/${endpoint}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
export { fetchData };
Now, let’s test this apiService:
// src/apiService.test.js
import { fetchData } from './apiService';
describe('apiService', () => {
const originalApiUrl = process.env.API_URL; // Store original value
beforeAll(() => {
// Set a specific API URL for all tests in this suite
process.env.API_URL = 'http://mock-api.com/v1';
});
afterAll(() => {
// Restore the original API URL after all tests are done
process.env.API_URL = originalApiUrl;
});
it('should fetch data from the mocked API', async () => {
const mockData = { message: 'Success' };
// Use global fetch mock (e.g., jest-fetch-mock)
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockData),
})
);
const data = await fetchData('users');
expect(global.fetch).toHaveBeenCalledWith('http://mock-api.com/v1/users');
expect(data).toEqual(mockData);
// Clean up fetch mock for subsequent tests if needed
global.fetch.mockRestore();
});
it('should handle API errors', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 404,
})
);
await expect(fetchData('nonexistent')).rejects.toThrow('HTTP error! status: 404');
// Clean up fetch mock
global.fetch.mockRestore();
});
});
In this example, we’re not just setting an environment variable; we’re using it to control the behavior of an external dependency (fetch). The beforeAll and afterAll hooks are crucial here to ensure that process.env.API_URL is set before the module that uses it is imported or executed within the test context, and that it’s restored afterward.
When you set process.env.NODE_ENV = 'test', you’re not just changing a string value. You are potentially altering the execution path within libraries or your own code that might have conditional logic like if (process.env.NODE_ENV === 'production') { /* do something */ }. Jest’s default configuration often sets NODE_ENV to 'test' automatically, but explicitly setting it gives you control and makes your test intent clear.
If you are using jest-runtime-env or dotenv-test, you are essentially abstracting away the direct process.env manipulation into configuration files, which can be more organized for larger projects. The underlying mechanism, however, remains the same: influencing the process.env object that your application code reads.
The next step after mastering environment variable testing is often exploring how to mock global objects like fetch or setTimeout in Jest, which are also fundamental to writing robust tests for asynchronous operations and timers.