如何用Jest测试Express+Mailgun邮件发送的成功状态?
如何用Jest测试Express+Mailgun的邮件发送功能
当然需要Mock Mailgun的发送行为!测试时你肯定不想真给销售团队发一堆测试邮件,而且依赖外部API会让测试变慢、不稳定,甚至因为API配额或密钥问题失败。下面一步步教你实现可靠的测试:
第一步:重构你的app.js,让它更易测试
原代码有几个不利于测试的问题:路由里重复require Mailgun、硬编码密钥、逻辑耦合严重。先做个小重构:
// app.js var express = require('express'); var bodyParser = require('body-parser'); // 把Mailgun初始化移到顶部,方便后续Mock var mailgun = require("mailgun-js"); var app = express(); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); // CORS中间件保持不变 app.use('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Accept, Origin, Content-Type, access_token'); res.header('Access-Control-Allow-Credentials', 'true'); next(); }); // 抽离Mailgun配置,也可以放到单独的.env文件里 const api_key = 'some key'; const DOMAIN = 'some Domain'; const mg = mailgun({ apiKey: api_key, domain: DOMAIN }); // 处理POST请求的路由 app.post('/get-client-data', function (req, res) { const postQuery = req.body.query; const name = postQuery.name; const email = postQuery.email; const message = postQuery.message; const data = { from: `Mailgun <postmaster@${DOMAIN}>`, to: 'Sales team <some@sales.io>', subject: `Name of client: ${name}, email: ${email}`, text: message, }; mg.messages().send(data, function (error, body) { if (error) { // 新增错误处理,原代码缺失这部分 console.error(error); return res.status(500).json({ response: 'Failed to send email' }); } console.log(body); res.json({ response: 'email has been sent' }); }); }); app.listen(3000, function () { console.log('backend is listening on port 3000!'); }); module.exports = app;
第二步:编写Jest测试用例
我们需要Mock mailgun-js模块,模拟它的发送逻辑,同时用supertest模拟HTTP请求测试路由。
先安装测试依赖:
npm install --save-dev jest supertest
创建测试文件app.test.js:
const request = require('supertest'); const app = require('./app'); // Mock整个mailgun-js模块 jest.mock('mailgun-js', () => { // 模拟send方法:默认触发成功回调 const mockSend = jest.fn((data, callback) => { callback(null, { message: 'Queued. Thank you.' }); }); const mockMessages = jest.fn(() => ({ send: mockSend })); // 返回模拟的Mailgun构造函数 return jest.fn(() => ({ messages: mockMessages })); }); describe('POST /get-client-data', () => { it('返回成功响应并验证邮件参数正确', async () => { const testPayload = { query: { name: 'John Doe', email: 'john@example.com', message: '我想了解你们的产品细节!' } }; const response = await request(app) .post('/get-client-data') .send(testPayload); // 验证HTTP响应 expect(response.statusCode).toBe(200); expect(response.body).toEqual({ response: 'email has been sent' }); // 验证Mailgun方法是否被正确调用 const mailgun = require('mailgun-js'); const mockMgInstance = mailgun.mock.instances[0]; const mockSend = mockMgInstance.messages.mock.results[0].value.send; expect(mockMgInstance.messages).toHaveBeenCalledTimes(1); expect(mockSend).toHaveBeenCalledTimes(1); expect(mockSend).toHaveBeenCalledWith( expect.objectContaining({ from: 'Mailgun <postmaster@some Domain>', subject: 'Name of client: John Doe, email: john@example.com', text: '我想了解你们的产品细节!' }), expect.any(Function) ); }); it('邮件发送失败时返回500错误', async () => { // 重新Mock,模拟错误场景 const mailgun = require('mailgun-js'); const mockSend = jest.fn((data, callback) => { callback(new Error('Mailgun API调用失败'), null); }); mailgun.mockImplementation(() => ({ messages: jest.fn(() => ({ send: mockSend })) })); const response = await request(app) .post('/get-client-data') .send({ query: { name: 'Jane', email: 'jane@test.com', message: '测试错误场景' } }); expect(response.statusCode).toBe(500); expect(response.body).toEqual({ response: 'Failed to send email' }); expect(mockSend).toHaveBeenCalledTimes(1); }); });
核心要点说明
- 必须Mock的原因:
- 避免发送真实邮件,防止骚扰收件人
- 摆脱对外部API的依赖,测试更快、更稳定,不用考虑密钥有效性或配额问题
- 轻松模拟成功/失败场景,覆盖所有代码分支
- 测试覆盖的关键点:
- HTTP响应的状态码和内容是否符合预期
- Mailgun的
send方法是否被调用,且传入的邮件参数正确 - 错误处理逻辑是否正常工作
内容的提问来源于stack exchange,提问作者Sanches Rodriguez
相关产品推荐
相关产品推荐

