5月28日 04:30
Koa testing framework selection and testing best practices
Koa testing is an important part of ensuring application quality. Through reasonable testing strategies and tools, you can ensure application stability and reliability.
1. Testing framework selection:
Common Koa testing frameworks include:
- Jest: Testing framework developed by Facebook, comprehensive features
- Mocha + Chai: Flexible testing framework combination
- Supertest: Library specifically for HTTP testing
2. Basic testing setup:
Install dependencies:
bashnpm install --save-dev jest supertest @types/jest @types/supertest
Jest configuration:
javascript// jest.config.js module.exports = { testEnvironment: 'node', coverageDirectory: 'coverage', collectCoverageFrom: [ 'src/**/*.js', '!src/**/*.test.js' ], testMatch: [ '**/__tests__/**/*.js', '**/?(*.)+(spec|test).js' ] };
3. Basic route testing:
javascriptconst request = require('supertest'); const app = require('../app'); describe('Basic routes', () => { test('GET / should return Hello Koa', async () => { const response = await request(app.callback()) .get('/') .expect(200); expect(response.text).toBe('Hello Koa'); }); test('GET /not-found should return 404', async () => { const response = await request(app.callback()) .get('/not-found') .expect(404); }); });
4. Middleware testing:
javascriptconst Koa = require('koa'); const loggerMiddleware = require('../middleware/logger'); describe('Logger middleware', () => { test('should log request information', async () => { const app = new Koa(); const logs = []; // Mock console.log const originalLog = console.log; console.log = (...args) => logs.push(args.join(' ')); app.use(loggerMiddleware); app.use(async (ctx) => { ctx.body = 'test'; }); await request(app.callback()).get('/test'); console.log = originalLog; expect(logs.length).toBeGreaterThan(0); expect(logs[0]).toContain('GET /test'); }); });
5. Authentication middleware testing:
javascriptconst authMiddleware = require('../middleware/auth'); describe('Auth middleware', () => { test('should allow access with valid token', async () => { const ctx = { headers: { authorization: 'Bearer valid-token' }, state: {} }; const next = jest.fn(); await authMiddleware(ctx, next); expect(next).toHaveBeenCalled(); expect(ctx.state.user).toBeDefined(); }); test('should deny access without token', async () => { const ctx = { headers: {}, state: {}, throw: jest.fn() }; const next = jest.fn(); await authMiddleware(ctx, next); expect(next).not.toHaveBeenCalled(); expect(ctx.throw).toHaveBeenCalledWith(401, 'Unauthorized'); }); });
6. API testing:
javascriptdescribe('User API', () => { test('POST /api/users should create user', async () => { const userData = { name: 'John Doe', email: 'john@example.com', password: 'password123' }; const response = await request(app.callback()) .post('/api/users') .send(userData) .expect(201); expect(response.body).toHaveProperty('id'); expect(response.body.name).toBe(userData.name); expect(response.body.email).toBe(userData.email); expect(response.body).not.toHaveProperty('password'); }); test('GET /api/users/:id should return user', async () => { const userId = 1; const response = await request(app.callback()) .get(`/api/users/${userId}`) .expect(200); expect(response.body).toHaveProperty('id', userId); expect(response.body).toHaveProperty('name'); expect(response.body).toHaveProperty('email'); }); test('PUT /api/users/:id should update user', async () => { const userId = 1; const updateData = { name: 'Updated Name' }; const response = await request(app.callback()) .put(`/api/users/${userId}`) .send(updateData) .expect(200); expect(response.body.name).toBe(updateData.name); }); test('DELETE /api/users/:id should delete user', async () => { const userId = 1; await request(app.callback()) .delete(`/api/users/${userId}`) .expect(204); }); });
7. Error handling testing:
javascriptdescribe('Error handling', () => { test('should handle 404 errors', async () => { const response = await request(app.callback()) .get('/non-existent-route') .expect(404); expect(response.body).toHaveProperty('error'); expect(response.body).toHaveProperty('code', 'NOT_FOUND'); }); test('should handle validation errors', async () => { const invalidData = { name: '', email: 'invalid-email' }; const response = await request(app.callback()) .post('/api/users') .send(invalidData) .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body).toHaveProperty('code', 'VALIDATION_ERROR'); }); test('should handle server errors', async () => { // Mock a database error jest.spyOn(User, 'create').mockRejectedValue(new Error('Database error')); const response = await request(app.callback()) .post('/api/users') .send({ name: 'Test', email: 'test@example.com' }) .expect(500); expect(response.body).toHaveProperty('error'); expect(response.body).toHaveProperty('code', 'INTERNAL_ERROR'); }); });
8. Integration testing:
javascriptdescribe('Integration tests', () => { let authToken; beforeAll(async () => { // Setup: create test user and get token const response = await request(app.callback()) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'password123' }); authToken = response.body.token; }); afterAll(async () => { // Cleanup: delete test data await request(app.callback()) .delete('/api/test/cleanup'); }); test('should create and retrieve post', async () => { // Create post const createResponse = await request(app.callback()) .post('/api/posts') .set('Authorization', `Bearer ${authToken}`) .send({ title: 'Test Post', content: 'Test content' }) .expect(201); const postId = createResponse.body.id; // Retrieve post const getResponse = await request(app.callback()) .get(`/api/posts/${postId}`) .expect(200); expect(getResponse.body.title).toBe('Test Post'); expect(getResponse.body.content).toBe('Test content'); }); });
9. Performance testing:
javascriptdescribe('Performance tests', () => { test('should handle 1000 requests in reasonable time', async () => { const startTime = Date.now(); const requests = []; for (let i = 0; i < 1000; i++) { requests.push( request(app.callback()) .get('/api/users') .expect(200) ); } await Promise.all(requests); const duration = Date.now() - startTime; expect(duration).toBeLessThan(5000); // Should complete in < 5 seconds }); });
10. Testing best practices:
-
Test organization:
- Organize test files by functional modules
- Use describe to group related tests
- Use meaningful test names
-
Test isolation:
- Each test should run independently
- Use beforeAll/afterAll for setup and cleanup
- Use beforeEach/afterEach to reset state
-
Mock and Stub:
- Mock external dependencies
- Use test database
- Avoid testing real network requests
-
Coverage:
- Set coverage goals (usually > 80%)
- Regularly check coverage reports
- Focus on critical path coverage
-
Continuous integration:
- Run tests in CI/CD pipeline
- Block deployment when tests fail
- Automatically generate coverage reports
-
Test data:
- Use factory pattern for test data
- Use fixed test data
- Clean up test data to avoid pollution
javascript// Test data factory const userFactory = (overrides = {}) => ({ name: 'Test User', email: 'test@example.com', password: 'password123', ...overrides }); // Use factory test('should create user', async () => { const userData = userFactory({ name: 'Custom Name' }); const response = await request(app.callback()) .post('/api/users') .send(userData) .expect(201); expect(response.body.name).toBe('Custom Name'); });