260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
---
|
||||
title: Mock External Services in Tests
|
||||
impact: HIGH
|
||||
impactDescription: Ensures fast, reliable, deterministic tests
|
||||
tags: testing, mocking, external-services, jest
|
||||
---
|
||||
|
||||
## Mock External Services in Tests
|
||||
|
||||
Never call real external services (APIs, databases, message queues) in unit tests. Mock them to ensure tests are fast, deterministic, and don't incur costs. Use realistic mock data and test edge cases like timeouts and errors.
|
||||
|
||||
**Incorrect (calling real APIs and databases):**
|
||||
|
||||
```typescript
|
||||
// Call real APIs in tests
|
||||
describe('PaymentService', () => {
|
||||
it('should process payment', async () => {
|
||||
const service = new PaymentService(new StripeClient(realApiKey));
|
||||
// Hits real Stripe API!
|
||||
const result = await service.charge('tok_visa', 1000);
|
||||
// Slow, costs money, flaky
|
||||
});
|
||||
});
|
||||
|
||||
// Use real database
|
||||
describe('UsersService', () => {
|
||||
beforeEach(async () => {
|
||||
await connection.query('DELETE FROM users'); // Modifies real DB
|
||||
});
|
||||
|
||||
it('should create user', async () => {
|
||||
const user = await service.create({ email: 'test@test.com' });
|
||||
// Side effects on shared database
|
||||
});
|
||||
});
|
||||
|
||||
// Incomplete mocks
|
||||
const mockHttpService = {
|
||||
get: jest.fn().mockResolvedValue({ data: {} }),
|
||||
// Missing error scenarios, missing other methods
|
||||
};
|
||||
```
|
||||
|
||||
**Correct (mock all external dependencies):**
|
||||
|
||||
```typescript
|
||||
// Mock HTTP service properly
|
||||
describe('WeatherService', () => {
|
||||
let service: WeatherService;
|
||||
let httpService: jest.Mocked<HttpService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
WeatherService,
|
||||
{
|
||||
provide: HttpService,
|
||||
useValue: {
|
||||
get: jest.fn(),
|
||||
post: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get(WeatherService);
|
||||
httpService = module.get(HttpService);
|
||||
});
|
||||
|
||||
it('should return weather data', async () => {
|
||||
const mockResponse = {
|
||||
data: { temperature: 72, humidity: 45 },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config: {},
|
||||
};
|
||||
|
||||
httpService.get.mockReturnValue(of(mockResponse));
|
||||
|
||||
const result = await service.getWeather('NYC');
|
||||
|
||||
expect(result).toEqual({ temperature: 72, humidity: 45 });
|
||||
});
|
||||
|
||||
it('should handle API timeout', async () => {
|
||||
httpService.get.mockReturnValue(
|
||||
throwError(() => new Error('ETIMEDOUT')),
|
||||
);
|
||||
|
||||
await expect(service.getWeather('NYC')).rejects.toThrow('Weather service unavailable');
|
||||
});
|
||||
|
||||
it('should handle rate limiting', async () => {
|
||||
httpService.get.mockReturnValue(
|
||||
throwError(() => ({
|
||||
response: { status: 429, data: { message: 'Rate limited' } },
|
||||
})),
|
||||
);
|
||||
|
||||
await expect(service.getWeather('NYC')).rejects.toThrow(TooManyRequestsException);
|
||||
});
|
||||
});
|
||||
|
||||
// Mock repository instead of database
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService;
|
||||
let repo: jest.Mocked<Repository<User>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockRepo = {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
createQueryBuilder: jest.fn(),
|
||||
};
|
||||
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
UsersService,
|
||||
{ provide: getRepositoryToken(User), useValue: mockRepo },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get(UsersService);
|
||||
repo = module.get(getRepositoryToken(User));
|
||||
});
|
||||
|
||||
it('should find user by id', async () => {
|
||||
const mockUser = { id: '1', name: 'John', email: 'john@test.com' };
|
||||
repo.findOne.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await service.findById('1');
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(repo.findOne).toHaveBeenCalledWith({ where: { id: '1' } });
|
||||
});
|
||||
});
|
||||
|
||||
// Create mock factory for complex SDKs
|
||||
function createMockStripe(): jest.Mocked<Stripe> {
|
||||
return {
|
||||
paymentIntents: {
|
||||
create: jest.fn(),
|
||||
retrieve: jest.fn(),
|
||||
confirm: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
},
|
||||
customers: {
|
||||
create: jest.fn(),
|
||||
retrieve: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
// Mock time for time-dependent tests
|
||||
describe('TokenService', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2024-01-15'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should expire token after 1 hour', async () => {
|
||||
const token = await service.createToken();
|
||||
|
||||
// Fast-forward time
|
||||
jest.advanceTimersByTime(61 * 60 * 1000);
|
||||
|
||||
expect(await service.isValid(token)).toBe(false);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Reference: [Jest Mocking](https://jestjs.io/docs/mock-functions)
|
||||
Reference in New Issue
Block a user