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 测试

Download history 11029/week @ 2024-03-14 8213/week @ 2024-03-21 8152/week @ 2024-03-28 13678/week @ 2024-04-04 12696/week @ 2024-04-11 14373/week @ 2024-04-18 13990/week @ 2024-04-25 19084/week @ 2024-05-02 16452/week @ 2024-05-09 32573/week @ 2024-05-16 30779/week @ 2024-05-23 34238/week @ 2024-05-30 34086/week @ 2024-06-06 37081/week @ 2024-06-13 32515/week @ 2024-06-20 27989/week @ 2024-06-27

139,668 每月下载量
3 crates 中使用

MIT 许可证

215KB
1.5K SLoC

Rust 822 SLoC // 0.0% comments JavaScript 487 SLoC // 0.1% comments Handlebars 248 SLoC // 0.0% comments

faux   最新版本 rustc 1.56+ docs

一个用于从结构体创建 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 模拟将自动实现 SendSync,如果实际实例也实现了它们。使用 #[derive(...)]CloneDebugDefault 进行操作也将按预期进行。其他可派生特质不受支持,因为它们关于数据(例如,EqHash),但 faux 是关于模拟行为而非数据。目前也不支持不是标准库一部分的特质。可以通过手动编写该特质的 impl 来解决这个问题。如果您相信有可派生的特质 faux 应该支持,请提交一个问题,解释您的用例。

Clone 是一个有点特殊的情况,因为它不会复制存根,而是与克隆实例共享它们。如果这不是克隆模拟所需的行为,您可以手动实现 Clone 并进行正常的方法存根(faux::when!(my_struct.clone()).then_return(/* something */))。注意,对于可耗尽的存根(例如,faux::when!(my_struct.foo().once())的情况,如果任一实例调用共享的存根,则视为耗尽存根。

与其他宏的交互

虽然 faux 不能保证它能与其它宏库兼容,但它应该“只是”能工作。然而也有一些注意事项。为了快速解决问题,尝试将 faux 属性(例如 #[faux::methods])设置为第一个属性。

解释

如果另一个 proc-macrofaux 执行宏扩展之前修改了方法签名,那么它可能会将签名修改成 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-traitasync-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