Testing Utilities Reference
Complete reference for NestJS Mailable testing utilities and mock factories.
Installation
Testing utilities are included in the main package and can be imported from:
import {
createMailServiceMock,
createSmtpTransportMock,
createTestModuleWithMockedMailService,
// ... other utilities
} from 'nestjs-mailable/testing';
Mail Service Mocks
createMailServiceMock()
Creates a basic mock MailService with all methods stubbed and returning sensible defaults.
import { createMailServiceMock } from 'nestjs-mailable/testing';
const mockMailService = createMailServiceMock();
// Use in tests
await mockMailService
.to('user@example.com')
.subject('Test')
.send();
expect(mockMailService.to).toHaveBeenCalledWith('user@example.com');
Mocked Methods:
to(),cc(),bcc()- Return the mock itself for chainingfrom(),replyTo()- Return the mock itself for chainingsubject(),html(),text()- Return the mock itself for chainingtemplate()- Return the mock itself for chainingsend()- ReturnsPromise<{ messageId: 'mock-msg-id' }>fake(),clearSent(),hasSent(),hasSentTo(),getSent()- Utility methodsverifyTransport()- ReturnsPromise<true>close()- ReturnsPromise<void>
createMailServiceMockWithError(error)
Creates a mock MailService that throws an error when send() is called.
const error = new Error('SMTP connection failed');
const mockMailService = createMailServiceMockWithError(error);
await expect(
mockMailService.to('user@example.com').send()
).rejects.toThrow('SMTP connection failed');
createMailServiceMockWithResponse(response)
Creates a mock MailService that returns a specific response.
const response = { messageId: 'msg-123', status: 'sent' };
const mockMailService = createMailServiceMockWithResponse(response);
const result = await mockMailService
.to('user@example.com')
.send();
expect(result).toEqual({ messageId: 'msg-123', status: 'sent' });
createMailServiceMockWithSequentialResponses(responses)
Creates a mock that returns different responses for sequential calls.
const mockMailService = createMailServiceMockWithSequentialResponses([
{ messageId: 'msg-1' },
{ messageId: 'msg-2' },
{ messageId: 'msg-3' },
]);
const result1 = await mockMailService.to('user1@example.com').send();
const result2 = await mockMailService.to('user2@example.com').send();
expect(result1.messageId).toBe('msg-1');
expect(result2.messageId).toBe('msg-2');
createMailServiceMockWithFake()
Creates a mock that tracks all "sent" emails (useful for email verification).
const mockMailService = createMailServiceMockWithFake();
await mockMailService.to('user1@example.com').send();
await mockMailService.to('user2@example.com').send();
const sentEmails = mockMailService.getSent();
expect(sentEmails).toHaveLength(2);
createConfiguredMailServiceMock(config)
Creates a mail service mock with custom configuration.
interface MockMailServiceConfig {
defaultResponse?: any;
shouldFail?: boolean;
failError?: Error;
sequentialResponses?: any[];
}
const mockMailService = createConfiguredMailServiceMock({
defaultResponse: { messageId: 'test-msg' },
shouldFail: false,
});
Transport Mocks
createSmtpTransportMock()
Creates a mock SMTP transport.
import { createSmtpTransportMock } from 'nestjs-mailable/testing';
const mockTransport = createSmtpTransportMock();
const result = await mockTransport.send({
to: 'user@example.com',
subject: 'Test',
html: '<p>Test</p>',
});
expect(result).toEqual({
messageId: '<test@example.com>',
response: '250 OK',
});
createSESTransportMock()
Creates a mock AWS SES transport.
const mockSESTransport = createSESTransportMock();
const result = await mockSESTransport.send({});
expect(result).toHaveProperty('MessageId');
expect(result.$metadata.httpStatusCode).toBe(200);
createMailgunTransportMock()
Creates a mock Mailgun transport.
const mockMailgunTransport = createMailgunTransportMock();
const result = await mockMailgunTransport.send({});
expect(result.id).toMatch(/mailgun-id/);
expect(result.message).toContain('Queued');
createMailjetTransportMock()
Creates a mock Mailjet transport.
const mockMailjetTransport = createMailjetTransportMock();
const result = await mockMailjetTransport.send({});
expect(result.Messages).toBeDefined();
expect(result.Messages[0].Status).toBe('success');
createResendTransportMock()
Creates a mock Resend transport.
const mockResendTransport = createResendTransportMock();
const result = await mockResendTransport.send({});
expect(result.id).toBe('resend-email-id-123456789');
expect(result.to).toBe('user@example.com');
createFailingTransportMock(error)
Creates a mock transport that fails.
const error = new Error('Connection timeout');
const mockTransport = createFailingTransportMock(error);
await expect(mockTransport.send({})).rejects.toThrow('Connection timeout');
createConfiguredTransportMock(config)
Creates a transport mock with custom configuration.
interface TransportMockConfig {
shouldFail?: boolean;
failError?: Error;
customResponse?: any;
verifyFailure?: boolean;
}
const mockTransport = createConfiguredTransportMock({
shouldFail: true,
failError: new Error('SMTP error'),
});
Module Mocks
createTestModuleWithMockedMailService(providers)
Creates a NestJS test module with mocked MailService.
import { createTestModuleWithMockedMailService } from 'nestjs-mailable/testing';
const module = await createTestModuleWithMockedMailService([UserService]);
const userService = module.get<UserService>(UserService);
const mailService = module.get<MailService>(MailService);
await userService.registerUser(user);
expect(mailService.to).toHaveBeenCalledWith(user.email);
createTestModuleWithFailingMailService(error, providers)
Creates a test module with a MailService that fails.
const error = new Error('SMTP error');
const module = await createTestModuleWithFailingMailService(
error,
[UserService]
);
const userService = module.get<UserService>(UserService);
await expect(userService.registerUser(user)).rejects.toThrow('SMTP error');
createTestModuleWithFakeMailService(providers)
Creates a test module with a fake MailService that tracks emails.
const module = await createTestModuleWithFakeMailService([UserService]);
const userService = module.get<UserService>(UserService);
const mailService = module.get<MailService>(MailService);
await userService.registerUser(user);
expect(mailService.getSent()).toHaveLength(1);
createConfiguredTestModule(config, providers)
Creates a test module with custom configuration.
interface ModuleMockConfig {
withMailService?: boolean;
shouldFail?: boolean;
failError?: Error;
withFake?: boolean;
customResponse?: any;
}
const module = await createConfiguredTestModule(
{
withMailService: true,
shouldFail: false,
customResponse: { messageId: 'test' },
},
[UserService]
);
TestModuleBuilder
Fluent builder for creating test modules.
import { TestModuleBuilder } from 'nestjs-mailable/testing';
const module = await new TestModuleBuilder()
.withMockedMailService()
.addProvider(UserService)
.addProvider(AuthService)
.build();
const userService = module.get<UserService>(UserService);
Methods:
withMockedMailService()- Add mocked MailServicewithFailingMailService(error)- Add MailService that failsaddProvider(provider)- Add a single provideraddProviders(providers)- Add multiple providersbuild()- Build and return the test module
Complete Examples
Testing a Service with Mail Functionality
import { Test } from '@nestjs/testing';
import { createMailServiceMock } from 'nestjs-mailable/testing';
import { UserService } from './user.service';
import { MailService } from 'nestjs-mailable';
describe('UserService', () => {
let userService: UserService;
let mailService: MailService;
beforeEach(async () => {
const mockMailService = createMailServiceMock();
const module = await Test.createTestingModule({
providers: [
UserService,
{ provide: MailService, useValue: mockMailService },
],
}).compile();
userService = module.get<UserService>(UserService);
mailService = module.get<MailService>(MailService);
});
it('should send welcome email on registration', async () => {
const user = { name: 'John', email: 'john@example.com' };
await userService.registerUser(user);
expect(mailService.to).toHaveBeenCalledWith(user.email);
expect(mailService.send).toHaveBeenCalled();
});
it('should handle email sending errors', async () => {
const error = new Error('Email service down');
jest.spyOn(mailService, 'send').mockRejectedValueOnce(error);
const user = { name: 'Jane', email: 'jane@example.com' };
await expect(userService.registerUser(user)).rejects.toThrow(
'Email service down'
);
});
});
Testing Multiple Email Sends
const module = await createTestModuleWithFakeMailService([UserService]);
const userService = module.get<UserService>(UserService);
const mailService = module.get<MailService>(MailService);
// Send to multiple users
const users = [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
{ email: 'user3@example.com', name: 'User 3' },
];
for (const user of users) {
await userService.sendNotification(user);
}
// Verify all emails were sent
expect(mailService.getSent()).toHaveLength(3);
users.forEach((user, index) => {
expect(mailService.getSent()[index].to).toBe(user.email);
});
Testing with Different Transports
import {
createSmtpTransportMock,
createSESTransportMock,
createMailgunTransportMock,
} from 'nestjs-mailable/testing';
describe('Multi-Transport Tests', () => {
it('should work with SMTP', async () => {
const transport = createSmtpTransportMock();
const result = await transport.send({});
expect(result.messageId).toBeDefined();
});
it('should work with SES', async () => {
const transport = createSESTransportMock();
const result = await transport.send({});
expect(result.MessageId).toBeDefined();
});
it('should work with Mailgun', async () => {
const transport = createMailgunTransportMock();
const result = await transport.send({});
expect(result.id).toBeDefined();
});
});
Testing Error Scenarios
const errorModule = await createTestModuleWithFailingMailService(
new Error('Network timeout'),
[UserService]
);
const userService = errorModule.get<UserService>(UserService);
it('should gracefully handle email failures', async () => {
const user = { email: 'user@example.com', name: 'User' };
await expect(
userService.registerUser(user)
).rejects.toThrow('Network timeout');
// Verify user was still created even though email failed
const createdUser = await userService.findByEmail(user.email);
expect(createdUser).toBeDefined();
});
Best Practices
1. Use Type-Safe Mocks
const mockMailService: jest.Mocked<MailService> = createMailServiceMock();
2. Reset Between Tests
beforeEach(() => {
jest.clearAllMocks();
mailService = createMailServiceMock();
});
3. Test Both Success and Failure
describe('Email sending', () => {
it('should handle success', async () => {
// Test success case
});
it('should handle failure', async () => {
const module = await createTestModuleWithFailingMailService(
new Error('Failed'),
[Service]
);
// Test failure case
});
});
4. Verify Fluent API Calls
await mailService
.to('user@example.com')
.cc('manager@example.com')
.subject('Test')
.send();
expect(mailService.to).toHaveBeenCalledWith('user@example.com');
expect(mailService.cc).toHaveBeenCalledWith('manager@example.com');
expect(mailService.subject).toHaveBeenCalledWith('Test');
expect(mailService.send).toHaveBeenCalled();