跳到主要内容

测试指南

本指南提供了使用 Contexify 构建的应用程序的测试说明。

概述

测试是软件开发的重要组成部分。Contexify 的依赖注入系统通过允许您模拟依赖项,使测试应用程序变得容易。

测试类型

在测试 Contexify 应用程序时,您通常会编写以下类型的测试:

  • 单元测试:隔离测试单个类或函数
  • 集成测试:测试多个组件如何一起工作
  • 端到端测试:从用户角度测试整个应用程序

单元测试

单元测试专注于隔离测试单个代码单元。在 Contexify 应用程序中,这通常意味着测试单个类或函数。

测试服务

以下是测试服务的示例:

import { Context } from 'contexify';
import { UserService } from '../services/user.service';
import { UserRepository } from '../repositories/user.repository';

describe('UserService', () => {
let context: Context;
let userService: UserService;
let mockUserRepository: jest.Mocked<UserRepository>;

beforeEach(() => {
// Create a mock repository
mockUserRepository = {
findById: jest.fn(),
create: jest.fn(),
} as unknown as jest.Mocked<UserRepository>;

// Create a context
context = new Context('test');

// Bind the mock repository
context.bind('repositories.UserRepository').to(mockUserRepository);

// Bind the service
context.bind('services.UserService').toClass(UserService);
});

it('should get a user by id', async () => {
// Arrange
const userId = '123';
const expectedUser = { id: userId, name: 'John Doe' };
mockUserRepository.findById.mockResolvedValue(expectedUser);

// Act
userService = await context.get('services.UserService');
const user = await userService.getUser(userId);

// Assert
expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
expect(user).toEqual(expectedUser);
});

it('should create a user', async () => {
// Arrange
const userData = { name: 'John Doe' };
const expectedUser = { id: '123', ...userData };
mockUserRepository.create.mockResolvedValue(expectedUser);

// Act
userService = await context.get('services.UserService');
const user = await userService.createUser(userData);

// Assert
expect(mockUserRepository.create).toHaveBeenCalledWith(userData);
expect(user).toEqual(expectedUser);
});
});

测试控制器

以下是测试控制器的示例:

import { Context } from 'contexify';
import { UserController } from '../controllers/user.controller';
import { UserService } from '../services/user.service';

describe('UserController', () => {
let context: Context;
let userController: UserController;
let mockUserService: jest.Mocked<UserService>;

beforeEach(() => {
// Create a mock service
mockUserService = {
getUser: jest.fn(),
createUser: jest.fn(),
} as unknown as jest.Mocked<UserService>;

// Create a context
context = new Context('test');

// Bind the mock service
context.bind('services.UserService').to(mockUserService);

// Bind the controller
context.bind('controllers.UserController').toClass(UserController);
});

it('should get a user by id', async () => {
// Arrange
const userId = '123';
const expectedUser = { id: userId, name: 'John Doe' };
mockUserService.getUser.mockResolvedValue(expectedUser);

// Act
userController = await context.get('controllers.UserController');
const user = await userController.getUser(userId);

// Assert
expect(mockUserService.getUser).toHaveBeenCalledWith(userId);
expect(user).toEqual(expectedUser);
});

it('should create a user', async () => {
// Arrange
const userData = { name: 'John Doe' };
const expectedUser = { id: '123', ...userData };
mockUserService.createUser.mockResolvedValue(expectedUser);

// Act
userController = await context.get('controllers.UserController');
const user = await userController.createUser(userData);

// Assert
expect(mockUserService.createUser).toHaveBeenCalledWith(userData);
expect(user).toEqual(expectedUser);
});
});

集成测试

集成测试专注于测试多个组件如何一起工作。在 Contexify 应用程序中,这通常意味着测试服务、仓库和其他组件如何交互。

import { Context } from 'contexify';
import { UserService } from '../services/user.service';
import { UserRepository } from '../repositories/user.repository';
import { DatabaseService } from '../services/database.service';

describe('User Integration', () => {
let context: Context;
let userService: UserService;
let databaseService: DatabaseService;

beforeEach(async () => {
// Create a context
context = new Context('test');

// Bind the database service
context.bind('services.DatabaseService').toClass(DatabaseService);

// Bind the repository
context.bind('repositories.UserRepository').toClass(UserRepository);

// Bind the service
context.bind('services.UserService').toClass(UserService);

// Get the database service
databaseService = await context.get('services.DatabaseService');

// Initialize the database
await databaseService.initialize();

// Get the user service
userService = await context.get('services.UserService');
});

afterEach(async () => {
// Clean up the database
await databaseService.cleanup();
});

it('should create and retrieve a user', async () => {
// Create a user
const userData = { name: 'John Doe' };
const createdUser = await userService.createUser(userData);

// Retrieve the user
const retrievedUser = await userService.getUser(createdUser.id);

// Assert
expect(retrievedUser).toEqual(createdUser);
});
});

端到端测试

端到端测试专注于从用户角度测试整个应用程序。在 Contexify 应用程序中,这通常意味着测试 API 端点或用户界面。

import { Application } from '../application';
import axios from 'axios';

describe('User API', () => {
let app: Application;
let baseUrl: string;

beforeAll(async () => {
// Create and start the application
app = new Application();
await app.start();

// Get the base URL
const port = app.getSync('config.port');
baseUrl = `http://localhost:${port}`;
});

afterAll(async () => {
// Stop the application
await app.stop();
});

it('should create a user', async () => {
// Create a user
const userData = { name: 'John Doe' };
const response = await axios.post(`${baseUrl}/users`, userData);

// Assert
expect(response.status).toBe(201);
expect(response.data).toHaveProperty('id');
expect(response.data.name).toBe(userData.name);
});

it('should get a user by id', async () => {
// Create a user
const userData = { name: 'Jane Doe' };
const createResponse = await axios.post(`${baseUrl}/users`, userData);
const userId = createResponse.data.id;

// Get the user
const getResponse = await axios.get(`${baseUrl}/users/${userId}`);

// Assert
expect(getResponse.status).toBe(200);
expect(getResponse.data).toEqual(createResponse.data);
});
});

测试组件

在测试组件时,您通常会测试组件如何与应用程序集成。

import { Context } from 'contexify';
import { LoggerComponent, LoggerBindings, Logger } from '../components/logger';

describe('LoggerComponent', () => {
let context: Context;
let logger: Logger;

beforeEach(async () => {
// Create a context
context = new Context('test');

// Add the logger component
const loggerComponent = new LoggerComponent({ level: 'debug', prefix: 'Test' });
for (const binding of loggerComponent.bindings) {
context.add(binding);
}

// Get the logger
logger = await context.get(LoggerBindings.SERVICE);
});

it('should log messages', () => {
// Mock console methods
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();

// Log a message
logger.info('Test message');

// Assert
expect(consoleSpy).toHaveBeenCalledWith('[Test] Test message');

// Restore console methods
consoleSpy.mockRestore();
});
});

测试拦截器

在测试拦截器时,您通常会测试它们如何修改方法行为。

import { Context, intercept, injectable } from 'contexify';
import { LogInterceptor } from '../interceptors/log.interceptor';

// Create a test class with an intercepted method
@injectable()
class TestService {
@intercept(LogInterceptor)
async testMethod(arg1: string, arg2: number): Promise<string> {
return `${arg1}-${arg2}`;
}
}

describe('LogInterceptor', () => {
let context: Context;
let testService: TestService;

beforeEach(async () => {
// Create a context
context = new Context('test');

// Bind the test service
context.bind('services.TestService').toClass(TestService);

// Get the test service
testService = await context.get('services.TestService');
});

it('should log method calls', async () => {
// Mock console methods
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();

// Call the intercepted method
const result = await testService.testMethod('hello', 123);

// Assert
expect(result).toBe('hello-123');
expect(consoleSpy).toHaveBeenCalledWith('Calling method: testMethod');
expect(consoleSpy).toHaveBeenCalledWith(
'Method testMethod returned:',
'hello-123'
);

// Restore console methods
consoleSpy.mockRestore();
});
});

最佳实践

  • 隔离测试:每个测试应该独立于其他测试
  • 模拟依赖项:使用模拟来隔离被测试的代码
  • 测试边缘情况:测试错误条件和边缘情况
  • 使用测试替身:根据需要使用间谍、存根和模拟
  • 清理:测试后清理资源
  • 使用测试夹具:使用夹具设置测试数据
  • 测试覆盖率:争取高测试覆盖率
  • 持续集成:在代码更改时自动运行测试

下一步

现在您已经了解了如何测试应用程序,可以了解: