Rust 用 mockall 做 Mock 测试:给 trait 接口写假实现并验证调用

test12026-10-210 次阅读

为什么需要 mock

业务代码依赖发邮件、访问数据库、调第三方服务时,单测不该真去连。我们会把这些依赖抽象成 trait,测试时塞一个“假实现”进去。手写假实现很啰嗦,mockall 能根据 trait 自动生成可配置的假对象,并且能断言“有没有被调用、用什么参数、调用几次”。

定义被依赖的 trait

use mockall::{mock, predicate::*};

pub trait Mailer {
    fn send(&self, to: &str, body: &str) -> Result<(), String>;
}

pub struct Notifier<'a> {
    mailer: &'a dyn Mailer,
}

impl<'a> Notifier<'a> {
    pub fn welcome(&self, email: &str) -> Result<(), String> {
        self.mailer.send(email, "Welcome!")
    }
}

用 mock! 生成假实现并验证

expect_send 设定期望:参数要等于某值、调用次数为 1、返回 Ok。测试结束后,mockall 会自动检查期望是否满足,没满足就报错。

mock! {
    MailerMock {}
    impl Mailer for MailerMock {
        fn send(&self, to: &str, body: &str) -> Result<(), String>;
    }
}

#[test]
fn sends_welcome_email() {
    let mut mock = MailerMock::new();
    mock.expect_send()
        .with(eq("a@b.com"), eq("Welcome!"))
        .times(1)
        .returning(|_, _| Ok(()));

    let notifier = Notifier { mailer: &mock };
    assert!(notifier.welcome("a@b.com").is_ok());
}

验证错误分支

换个测试,让 mock 返回 Err,断言业务方把错误正确透传。这样成功与失败两条链路都能覆盖,且不依赖真实邮件服务。

#[test]
fn surfaces_send_failure() {
    let mut mock = MailerMock::new();
    mock.expect_send()
        .returning(|_, _| Err("smtp down".into()));

    let notifier = Notifier { mailer: &mock };
    assert_eq!(notifier.welcome("a@b.com"), Err("smtp down".into()));
}

配合 thiserror 定义库错误、anyhow 收口应用层错误时,mock 的返回值直接是 Result,测试写起来和线上代码一致,没有额外心智负担。

T

test1

文章作者

为什么需要 mock 业务代码依赖发邮件、访问数据库、调第三方服务时,单测不该真去连。我们会把这些依赖抽象成 trait...

分类
技术
发布时间
2026-10-21
字数
约 1484 字
阅读
0 次