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日 |
1393 在 进程宏 中
51,503 每月下载量
在 4 个crate中使用(通过 faux)
49KB
1K SLoC
faux −
一个从结构体创建模拟的库。
faux
允许你在测试时模拟结构体的方法,而不使你的代码变得复杂或混乱。
有关更多信息,请参阅 API文档。
入门
faux
充分利用了不安全的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 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>
) - 外部模块中的方法(但不是外部包)。
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]
)作为第一个属性。
说明
尽管faux
不能保证它与其他宏库一起工作,但它应该“仅仅”工作。然而,也有一些注意事项。为了快速解决问题,请尝试将faux
属性(例如,#[faux::methods]
)作为第一个属性。
#[faux::create]
struct Foo { /*some items here */ }
#[faux::methods]
#[another_attribute]
impl Foo {
/* some methods here */
}
如果另一个proc-macro
在faux
进行宏扩展之前修改了一个方法签名,那么它可能会修改签名,使其不再被faux
支持。遗憾的是,proc宏的顺序并未指定。然而,在实践中,它似乎是从上到下展开的(在Rust 1.42中进行了测试)。
在上面的代码片段中,#[faux::methods]
将首先展开,然后是#[another_attribute]
。faux
实际上忽略了其他宏,并根据您编写的代码进行展开。
如果#[faux::methods]
在另一个宏修改了impl
块之后执行其展开,则#[faux::methods]
将接收展开后的代码。此代码可能包含与您最初编写的代码不同的方法签名。请注意,其他proc宏的展开可能会创建faux
无法处理的代码(例如,显式生命周期)。
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 */
}
}
如果您发现一个 faux
无法处理的进程宏,请提出一个问题,看看 faux
是否正在执行一些意外的操作,与该宏冲突。
目标
faux
建立在这样一个信念上:具有单个实现的特质是一种不必要的负担和不必要的抽象层。因此,faux
不依赖于每个模拟对象的特质定义,这会在它们的函数签名中引入泛型或特质对象。 faux
旨在从用户定义的结构体中创建模拟,避免只为测试而存在的额外生产代码。
灵感
这个库受到了 mocktopus 的启发,这是一个为 nightly Rust 设计的模拟库,允许您模拟任何函数。与 mocktopus 不同,faux
在 stable Rust 上运行,并且故意只允许模拟结构体中的公共方法。
依赖项
~0.8–1.3MB
~28K SLoC