Method-level mocking for Egg.js services with support for custom implementations, error simulation, and both synchronous and asynchronous operations.
Mock individual service methods with custom implementations or return values.
/**
* Mock a service method
* @param {string|Object} service - Service name (dot-notation) or service class
* @param {string} methodName - Method name to mock
* @param {Function|Object|Error} fn - Mock implementation or return value
* @returns {Application} Application instance for chaining
*/
app.mockService(service, methodName, fn);Usage Examples:
// Mock service method with return value
app.mockService('user', 'find', {
id: 123,
name: 'Alice',
email: 'alice@example.com'
});
// Mock service method with function
app.mockService('user', 'create', (userData) => {
return {
id: Math.floor(Math.random() * 1000),
...userData,
createdAt: new Date()
};
});
// Mock nested service method
app.mockService('user.profile', 'getById', {
userId: 123,
avatar: 'avatar.jpg',
bio: 'Software developer'
});
// Mock with async function
app.mockService('email', 'send', async (to, subject, body) => {
await new Promise(resolve => setTimeout(resolve, 100));
return { messageId: 'msg-123', status: 'sent' };
});
// Mock with generator function
app.mockService('data', 'process', function* (data) {
yield { status: 'processing' };
return { status: 'completed', result: data.length };
});Mock service methods to throw errors for testing error handling scenarios.
/**
* Mock a service method to return an error
* @param {string|Object} service - Service name (dot-notation) or service class
* @param {string} methodName - Method name to mock
* @param {Error|string} [err] - Error to throw (creates default if not provided)
* @returns {Application} Application instance for chaining
*/
app.mockServiceError(service, methodName, err);Usage Examples:
// Mock service to throw default error
app.mockServiceError('user', 'find');
// Throws: Error('mock find error')
// Mock service with custom error message
app.mockServiceError('user', 'create', 'User creation failed');
// Mock service with custom Error object
app.mockServiceError('user', 'delete', new Error('Permission denied'));
// Mock service with specific error type
app.mockServiceError('payment', 'charge', new TypeError('Invalid card number'));
// Mock service with error containing additional properties
const error = new Error('Database connection failed');
error.code = 'ECONNREFUSED';
error.statusCode = 503;
app.mockServiceError('database', 'query', error);Mock methods on service classes and prototype methods.
/**
* Mock service using service class reference
* @param {Object} service - Service class or prototype
* @param {string} methodName - Method name to mock
* @param {Function|Object|Error} fn - Mock implementation or return value
*/
app.mockService(serviceClass, methodName, fn);Usage Examples:
// Mock using service class
const UserService = app.serviceClasses.user;
app.mockService(UserService, 'findById', { id: 123, name: 'Alice' });
// Mock prototype method
const UserService = app.serviceClasses.user;
app.mockService(UserService.prototype, 'validate', true);
// Mock nested service class
const ProfileService = app.serviceClasses.user.profile;
app.mockService(ProfileService, 'update', (id, data) => {
return { id, ...data, updatedAt: new Date() };
});Automatic handling of synchronous and asynchronous method mocking with proper return type conversion.
Usage Examples:
// Mock async method with sync return
app.mockService('user', 'findAsync', { id: 123 });
// Original method is async, but mock returns sync value
// egg-mock automatically wraps in Promise
// Mock sync method with async function
app.mockService('validator', 'checkSync', async (data) => {
await new Promise(resolve => setTimeout(resolve, 10));
return data.isValid;
});
// Original method is sync, but mock is async
// egg-mock handles the conversion
// Mock generator function
app.mockService('processor', 'runGenerator', function* () {
yield 'step1';
yield 'step2';
return 'completed';
});
// Mock async generator function
app.mockService('stream', 'processAsync', async function* () {
for (let i = 0; i < 3; i++) {
await new Promise(resolve => setTimeout(resolve, 10));
yield `item-${i}`;
}
});Chain multiple service mocks and manage complex service interactions.
Usage Examples:
// Chain multiple service mocks
app.mockService('user', 'find', { id: 123, name: 'Alice' })
.mockService('user', 'create', { id: 124, name: 'Bob' })
.mockService('email', 'send', { status: 'sent' });
// Mock related services
app.mockService('user', 'findById', {
id: 123,
name: 'Alice',
profileId: 456
});
app.mockService('profile', 'findById', {
id: 456,
userId: 123,
avatar: 'alice.jpg'
});
// Mock service dependencies
app.mockService('payment', 'charge', (amount, cardToken) => {
// Mock depends on other services
return {
id: 'charge-123',
amount,
status: 'succeeded',
cardLast4: '4242'
};
});
app.mockService('notification', 'send', async (userId, message) => {
return { delivered: true, timestamp: Date.now() };
});Individual service mock restoration and verification.
Usage Examples:
// Mock services for specific test
beforeEach(() => {
app.mockService('user', 'find', { id: 123 });
app.mockService('email', 'send', { sent: true });
});
// Restore all mocks after test
afterEach(() => {
app.mockRestore();
});
// Verify service calls in test
it('should call user service', async () => {
let findCalled = false;
app.mockService('user', 'find', (id) => {
findCalled = true;
return { id, name: 'Alice' };
});
const user = await app.service.user.find(123);
assert(findCalled);
assert.equal(user.id, 123);
});Mock services that depend on context or request data.
Usage Examples:
// Mock service that uses context
app.mockService('auth', 'getCurrentUser', function() {
// 'this' refers to the context in service methods
return {
id: this.userId || 123,
name: 'Alice',
role: this.userRole || 'user'
};
});
// Mock context-dependent service
const ctx = app.mockContext({ userId: 456, userRole: 'admin' });
app.mockService('permission', 'check', function(resource) {
return this.userRole === 'admin';
});
// Test context-dependent service
it('should respect context in service mock', async () => {
const ctx = app.mockContext({ userId: 789 });
app.mockService('audit', 'log', function(action) {
return {
userId: this.userId,
action,
timestamp: Date.now()
};
});
const result = await ctx.service.audit.log('login');
assert.equal(result.userId, 789);
});Create dynamic service mocks that change behavior based on input or state.
Usage Examples:
// Dynamic return based on input
app.mockService('user', 'find', (id) => {
if (id === 123) {
return { id: 123, name: 'Alice', active: true };
} else if (id === 124) {
return { id: 124, name: 'Bob', active: false };
} else {
throw new Error('User not found');
}
});
// Stateful service mock
let callCount = 0;
app.mockService('counter', 'increment', () => {
return ++callCount;
});
// Mock with side effects
const emails = [];
app.mockService('email', 'send', (to, subject, body) => {
emails.push({ to, subject, body, sentAt: Date.now() });
return { messageId: `msg-${emails.length}` };
});
// Verify side effects
it('should track email sends', async () => {
await app.service.email.send('test@example.com', 'Hello', 'World');
assert.equal(emails.length, 1);
assert.equal(emails[0].to, 'test@example.com');
});