在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
相关产品推荐
相关产品推荐

