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

在Rust单元测试中处理MongoDB的#[non_exhaustive]结构体UpdateResult

解决Rust中MongoDB #[non_exhaustive]结构体在单元测试中的Mock问题

问题背景

在基于Rust和MongoDB的项目里,用mockall对UserRepository做单元测试时,碰到MongoDB的UpdateResult被标记为#[non_exhaustive],没法直接用结构体表达式实例化,导致没法Mock返回结果。官方JIRA上的RUST-1891问题还没解决,得自己找办法绕过去。

可行解决方案

1. 用MongoDB库自带的构造方法(优先选这个)

很多#[non_exhaustive]的结构体都会提供公开的构造函数,比如UpdateResult就有acknowledged方法,直接用它创建实例就行:

let mock_update_result = UpdateResult::acknowledged(1, 1, None);

这是官方提供的方式,安全且不会因为库版本更新失效。

2. 利用Default trait生成实例后修改字段

如果结构体实现了Default,可以先创建一个默认实例,再修改需要的字段:

let mut mock_update_result = UpdateResult::default();
mock_update_result.matched_count = 1;
mock_update_result.modified_count = 1;
mock_update_result.upserted_id = None;

这种方式不需要依赖额外构造方法,只要结构体有公开字段就能用。

3. 自定义结果类型解耦(适合长期维护)

如果不想依赖MongoDB的具体类型,还可以修改UserRepository trait,让它返回自定义的结果结构体,而不是直接返回UpdateResult:

// 自定义结果类型,测试时可以自由实例化
#[derive(Debug, Clone)]
pub struct UpdateUserResult {
    pub matched_count: u64,
    pub modified_count: u64,
    pub upserted_id: Option<Bson>,
}

// 修改trait
#[automock]
#[async_trait]
pub trait UserRepository {
    async fn update_one(
        &self,
        query: Document,
        update: Document,
        options: Option<UpdateOptions>,
    ) -> Result<UpdateUserResult, Error>;
    // ... 其他方法
}

// 在MongoUserRepository实现中转换类型
#[async_trait]
impl UserRepository for MongoUserRepository {
    async fn update_one(
        &self,
        query: Document,
        update: Document,
        options: Option<UpdateOptions>,
    ) -> Result<UpdateUserResult, Error> {
        let result = self.collection.update_one(query, update, options).await?;
        Ok(UpdateUserResult {
            matched_count: result.matched_count,
            modified_count: result.modified_count,
            upserted_id: result.upserted_id,
        })
    }
    // ... 其他方法实现
}

这样测试时就能直接实例化UpdateUserResult,完全不受#[non_exhaustive]限制,还能让业务逻辑和MongoDB的具体实现解耦。

4. 最后手段:unsafe代码(不推荐)

如果以上方法都不行,才考虑用unsafe绕过限制,但这会破坏Rust的安全保障,库更新后可能失效:

let mock_update_result = unsafe {
    std::mem::transmute::<_, UpdateResult>((1u64, 1u64, None::<Bson>))
};

不到万不得已别用这个。

修改后的测试代码示例

用第一种方法(官方构造函数)的测试代码:

#[cfg(test)]
mod tests {
    use mongodb::results::UpdateResult;
    use crate::{
        modules::user::{repository::MockUserRepository, service::UserService},
        tests::unit::common::get_user_to_test,
    };

    #[tokio::test]
    async fn check_if_set_session_id_in_user() {
        let mut mock_repo = MockUserRepository::new();

        // 用官方构造方法创建实例
        let mock_update_result = UpdateResult::acknowledged(1, 1, None);

        mock_repo
            .expect_update_one()
            .returning(move |_, _, _| Ok(mock_update_result));

        let user_service = UserService::new(mock_repo);
        let mut user = get_user_to_test();
        user.session_id = None;

        assert!(user.session_id.is_none());
        let result = user_service.check_or_add_session(&mut user).await;

        assert!(result.is_ok());
        assert!(user.session_id.is_some());
    }
}

内容的提问来源于stack exchange,提问作者rick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:14:52