You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:45:39