#mocking #faux #testing

faux_macros

#[create],#[methods],when!的实现

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进程宏

Download history 13449/week @ 2024-04-15 13037/week @ 2024-04-22 16692/week @ 2024-04-29 18128/week @ 2024-05-06 18645/week @ 2024-05-13 37298/week @ 2024-05-20 29556/week @ 2024-05-27 35126/week @ 2024-06-03 35920/week @ 2024-06-10 33720/week @ 2024-06-17 35928/week @ 2024-06-24 24601/week @ 2024-07-01 14626/week @ 2024-07-08 12020/week @ 2024-07-15 11705/week @ 2024-07-22 12833/week @ 2024-07-29

51,503 每月下载量
4 个crate中使用(通过 faux

MIT 许可证

49KB
1K SLoC

faux − 最新版本 rustc 1.58+ docs

一个从结构体创建模拟的库。

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 模拟将自动实现 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])作为第一个属性。

说明

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

#[faux::create]
struct Foo { /*some items here */ }

#[faux::methods]
#[another_attribute]
impl Foo {
    /* some methods here */
}

如果另一个proc-macrofaux进行宏扩展之前修改了一个方法签名,那么它可能会修改签名,使其不再被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