21 个版本
0.1.10 | 2023 年 8 月 16 日 |
---|---|
0.1.9 | 2022 年 9 月 19 日 |
0.1.7 | 2022 年 6 月 22 日 |
0.1.6 | 2022 年 2 月 24 日 |
0.0.4 | 2020 年 2 月 12 日 |
#28 in 测试
139,668 每月下载量
在 3 crates 中使用
215KB
1.5K SLoC
faux

一个用于从结构体创建 mocks 的库。
faux
允许您模拟结构体的方法进行测试,而不会使您的代码变得复杂或污染。
有关更多信息,请参阅 API 文档。
入门
faux
充分利用了 unsafe Rust 功能,因此仅建议在测试中使用。为了防止 faux
溢出到您的生产代码中,在您的 Cargo.toml
中将其设置为 dev-dependency
[dev-dependencies]
faux = "^0.1"
faux
提供了两个属性
#[create]
: 将结构体转换为可模拟的等效形式#[methods]
: 将impl
块中的方法转换为它们的可模拟等效形式
使用 Rust 的 #[cfg_attr(...)]
将这些属性限制在测试配置中。
#[cfg_attr(test, faux::create)]
pub struct MyStructToMock { /* fields */ }
#[cfg_attr(test, faux::methods)]
impl MyStructToMock { /* methods to mock */ }
示例
mod client {
// #[faux::create] makes a struct mockable and
// generates an associated `faux` function
// e.g., `UserClient::faux()` will create a a mock `UserClient` instance
#[faux::create]
pub struct UserClient { /* data of the client */ }
#[derive(Clone)]
pub struct User {
pub name: String
}
// #[faux::methods] makes every public method in the `impl` block mockable
#[faux::methods]
impl UserClient {
pub fn fetch(&self, id: usize) -> User {
// does some network calls that we rather not do in tests
User { name: "".into() }
}
}
}
use crate::client::UserClient;
pub struct Service {
client: UserClient,
}
#[derive(Debug, PartialEq)]
pub struct UserData {
pub id: usize,
pub name: String,
}
impl Service {
fn user_data(&self) -> UserData {
let id = 3;
let user = self.client.fetch(id);
UserData { id, name: user.name }
}
}
// A sample #[test] for Service that mocks the client::UserClient
fn main() {
// create a mock of client::UserClient using `faux`
let mut client = client::UserClient::faux();
// mock fetch but only if the argument is 3
// argument matchers are optional
faux::when!(client.fetch(3))
// stub the return value for this mock
.then_return(client::User { name: "my user name".into() });
// prepare the subject for your test using the mocked client
let subject = Service { client };
// assert that your subject returns the expected data
let expected = UserData { id: 3, name: String::from("my user name") };
assert_eq!(subject.user_data(), expected);
}
由于 rustdocs 的限制,上面的示例测试是在 main()
中进行的,而不是一个 #[test]
函数。在实际应用中,伪造属性应该被限制在 #[cfg(test)]
。
功能
faux
允许您模拟异步方法、特质方法、泛型结构体方法以及指针自我类型(例如,self: Rc<Self>
)的返回值或实现。
- 异步方法
- 特质方法
- 泛型结构体方法
- 具有指针自我类型的(例如,
self: Rc<Self>
)的方法 - 外部模块中的方法(但不包括外部包)。
faux
还提供了易于使用的参数匹配器。
与 #[derive(...)]
和自动特质交互。
faux
模拟将自动实现 Send
和 Sync
,如果实际实例也实现了它们。使用 #[derive(...)]
对 Clone
、Debug
和 Default
进行操作也将按预期进行。其他可派生特质不受支持,因为它们关于数据(例如,Eq
或 Hash
),但 faux
是关于模拟行为而非数据。目前也不支持不是标准库一部分的特质。可以通过手动编写该特质的 impl
来解决这个问题。如果您相信有可派生的特质 faux
应该支持,请提交一个问题,解释您的用例。
Clone
是一个有点特殊的情况,因为它不会复制存根,而是与克隆实例共享它们。如果这不是克隆模拟所需的行为,您可以手动实现 Clone
并进行正常的方法存根(faux::when!(my_struct.clone()).then_return(/* something */)
)。注意,对于可耗尽的存根(例如,faux::when!(my_struct.foo().once()
)的情况,如果任一实例调用共享的存根,则视为耗尽存根。
与其他宏的交互
虽然 faux
不能保证它能与其它宏库兼容,但它应该“只是”能工作。然而也有一些注意事项。为了快速解决问题,尝试将 faux
属性(例如 #[faux::methods]
)设置为第一个属性。
解释
如果另一个 proc-macro
在 faux
执行宏扩展之前修改了方法签名,那么它可能会将签名修改成 faux
不支持的形式。不幸的是,proc宏的顺序没有指定。然而,在实践中,它似乎是自顶向下扩展的(在 Rust 1.42 中进行了测试)。
#[faux::create]
struct Foo { /*some items here */ }
#[faux::methods]
#[another_attribute]
impl Foo {
/* some methods here */
}
在上面的代码片段中,#[faux::methods]
会先于 #[another_attribute]
展开。faux
实际上忽略其他宏,并根据你编写的代码进行扩展。
如果 #[faux::methods]
在另一个宏修改了 impl
块之后进行扩展,那么 #[faux::methods]
将收到扩展后的代码。这段代码可能包含与你最初编写的不同方法签名。请注意,其他proc宏的扩展可能会创建 faux
无法处理的代码(例如显式生命周期)。
让我们通过一个具体的例子来看看 async-trait
。 async-trait
实际上转换
async fn run(&self, arg: Arg) -> Out {
/* stuff inside */
}
fn run<'async>(&'async self, arg: Arg) -> Pin<Box<dyn std::future::Future<Output = Out> + Send + 'async>> {
/* crazier stuff inside */
}
由于 async-trait
为方法签名添加了显式生命周期,这是 faux
无法处理的,因此让 async-trait
先进行扩展会导致 faux
失效。请注意,即使 faux
能够处理显式生命周期,我们的签名现在如此难以管理,以至于它会使模拟变得难以使用。因为 async-trait
只需要一个 async
函数签名,而 faux
不会修改函数签名,所以让 faux
先扩展是可以的。
#[faux::methods]
#[async_trait]
impl MyStruct for MyTrait {
async fn run(&self, arg: Arg) -> Out {
/* stuff inside */
}
}
如果你发现有一个proc宏 faux
无法处理,请提出一个问题,看看 faux
是否正在执行一些意外的操作,与该宏冲突。
目标
faux
是基于这样的信念而创建的:具有单一实现的特质的负担是不必要的,并且是一个不必要的抽象层。因此,faux
不依赖于每个模拟对象的特质定义,这会将泛型或特质对象污染到其函数签名中。 faux
的目标是使用用户定义的结构体创建模拟,避免仅存在于测试中的额外生产代码。
灵感
这个库受到了 mocktopus 的启发,这是一个为 nightly Rust 设计的模拟库,允许你模拟任何函数。与 mocktopus 不同,faux
在 stable Rust 上运行,并且故意只允许在结构体中模拟公共方法。
依赖项
~0.8–1.3MB
~28K SLoC