mockall
板条箱来模拟一个特征:
#[automock]
trait Foo {
fn foo(input: &Vec<String>) -> Option<&String>;
}
但是,我收到以下错误:
error[E0637]: `&` without an explicit lifetime name cannot be used here
--> src/names_matcher.rs:79:51
|
79 | fn foo(input: &Vec<String>) -> Option<&String>;
| ^ explicit lifetime name needed here
error[E0623]: lifetime mismatch
--> src/names_matcher.rs:77:9
|
77 | #[automock]
| ^^^^^^^^^^^
| |
| ...but data from `input` is returned here
78 | trait Foo {
79 | fn foo(input: &Vec<String>) -> Option<&String>;
| ------------ this parameter and the return type are declared with different lifetimes...
我想要实现的函数将返回 None 或 Some,并引用输入中向量的元素之一。如果我尝试定义生命周期并考虑到这一点:
#[automock]
trait Foo {
fn foo<'r>(input: &'r Vec<String>) -> Option<&'r String>;
}
我得到以下信息:
error[E0261]: use of undeclared lifetime name `'r`
--> src/names_matcher.rs:79:20
|
79 | fn foo<'r>(input: &'r Vec<String>) -> Option<&'r String>;
| ^^ undeclared lifetime
|
help: consider introducing lifetime `'r` here
|
77 | #[automock]<'r>
| ^^^^
help: consider introducing lifetime `'r` here
|
77 | 'r, #[automock]
| ^^^
但是这些建议都不起作用,它们会产生语法错误。有没有一种方法可以模拟我上面定义的特征?
虽然问题据说在返回
Option<&String>
类型时存在问题,但由于该特征定义了静态方法,并且由于期望是全局的,因此有 特殊考虑因素,因此问题也变得复杂,因此以下答案没有解决这个问题但仅解决与特征相关的模拟的定义部分,并且下面的演示没有使用建议的互斥体进行同步,因为这个问题与此无关。
首先,该特质应该是独立的,如下所示:
trait Foo {
fn foo(input: &Vec<String>) -> Option<&String>;
}
其次,创建模拟,我将在
Bar
宏中调用模拟mock!
;定义后会变成MockBar
。 请注意,使用 'static
生命周期作为返回引用类型不会与特征定义冲突 - 请注意,mockall
的创建者在 this comment 中指出,处理非 'static'
生命周期很困难,并指定 'static
生命周期作为解决方法。
use mockall::mock;
mock! {
pub Bar {}
impl Foo for Bar {
fn foo(input: &Vec<String>) -> Option<&'static String>;
}
}
最后进行测试时,必须提供具有
'static
寿命的参考。 从 Rust 1.70.0 开始,这可以通过 OnceLock
以安全的方式轻松完成。
use mockall::predicate::eq;
use std::sync::OnceLock;
static TEST_SUCCESS_INPUT: OnceLock<Vec<String>> = OnceLock::new();
static TEST_SUCCESS_RESULT: OnceLock<String> = OnceLock::new();
#[test]
fn test_success() {
let input = vec!["abc".to_string()];
let result = "result".to_string();
TEST_SUCCESS_INPUT.set(input.clone())
.expect("OnceLock for test_success's input was set elsewhere");
TEST_SUCCESS_RESULT.set(result.clone())
.expect("OnceLock for test_success's result was set elsewhere");
let mock = MockBar::foo_context();
mock.expect()
.times(1)
.with(eq(TEST_SUCCESS_INPUT.get()
.expect("the input value was not already set")))
.returning(|_| Some(TEST_SUCCESS_RESULT.get()
.expect("the return value was not already set")));
assert_eq!(MockBar::foo(&input), Some(&result));
}
这类似于“模拟返回引用的泛型方法”所采用的方法,它少了一个问题,即不必处理同步问题,并且在未满足所有期望时可能会导致测试输出混乱。 就我个人而言,我只会为每个测试构建一个特定的模拟来解决这个问题。