#mocking #unit-testing

mock_derive

为Rust编程语言提供一个易于设置、功能丰富的模拟库

9 个版本 (5 个破坏性更新)

使用旧的Rust 2015

0.8.0 2020年10月17日
0.7.0 2017年11月17日
0.6.1 2017年9月13日
0.5.0 2017年7月19日
0.3.1 2017年7月16日

过程宏 中排名 1682

每月下载量 44

MIT 许可证

41KB
767 行代码(不包括注释)

Follow us out on Crates.io Build Status

Mock_Derive 是一个易于设置、功能丰富的Rust编程语言模拟库。当你与另一个测试系统(如 cargo test)结合使用时,它可以让你快速设置单元测试。

为了安装,只需将以下行添加到你的Cargo.toml中

[dependencies]
mock_derive = "0.8.0"

请注意,mock_derive 不是一个1.0版本的crate,仍在积极开发中。因此,你可能发现一些尚未支持的实际用例。如果你发现这样的用例,请提交一个issue,我们将尽快处理。

mock_dervice 于2017年开发,但由于主要贡献者的生活情况,开发暂停。2020年,开发重新开始。

Mock_Derive 与其他语言中的模拟库有何不同。

在传统的OO语言中,模拟通常基于继承,或者在更动态的语言中混合方法替换。你从一个模拟工厂创建一个 Foo,定义那个 Foo 的行为,并将其传递给期望一个 Foo 的函数。Rust没有传统的继承,这意味着“只有Foo是Foo”。Mock_Derive 鼓励实现模拟。这意味着你将为一个特质推导出你的模拟。你将把这个模拟传递给期望实现那个特质的函数,并且你将能够控制该模拟的行为,类似于你过去可能使用过的其他模拟库。

示例

查看src/examples以获取工作示例

使用此crate看起来像这样

#![feature(proc_macro)]
extern crate mock_derive;

use mock_derive::mock;

#[mock]
pub trait CustomTrait {
    fn get_int(&self) -> u32;
    fn opt_int(&self) -> Option<u32>;
    fn default_method(&self, x: i32, y: i32) -> i32 {
        x + y
    }
}

您会注意到,在我们的特质定义上方包含了一个#[mock]指令。默认情况下,这将生成一个名为"MockCustomTrait"的CustomTrait实现,它包含用于控制其行为的辅助函数。例如,我们可以编写以下测试函数:

#[test]
fn it_works() {
    let foo = Foo::new(); // Foo here is a struct that implements CustomTrait
    let mut mock = MockCustomTrait::new();
    mock.set_fallback(foo); // If a behavior isn't specified, we will fall back to this object's behavior.

    let method = mock.method_get_int()
        .first_call()
        .set_result(3)
        .second_call()
        .set_result(4)
	.nth_call(3) // This is saying 'third_call'
	.set_result(5);


    mock.set_get_int(method); // Due to Rust's ownership model, we will need to set our mock method
                              // on our mock
    let result = mock.get_int();
    assert!(result == 3);
    let result2 = mock.get_int();
    assert!(result2 == 4);
    let result3 = mock.get_int();
    assert!(result3 == 5);

    // This is a fallback case
    let result4 = mock.get_int();
    assert!(result4 == 1);
}

// You can also pass in a lambda to return a value. This can be used to return a value
// an infinite number of times, or mutate state to simulate an object across calls.
#[test]
fn return_result_of() {
    let mut x = 15;
    let mut mock = MockCustomTrait::new();
    let method = mock.method_opt_int()
        .return_result_of(move || {
            x += 1;
            Some(x)
        });

    mock.set_opt_int(method);
    assert!(mock.opt_int() == Some(16));
    assert!(mock.opt_int() == Some(17));
    assert!(mock.opt_int() == Some(18));
}

// You can also specify the total number of calls (i.e. once, exactly 5 times, at least 5 times, at most 10 times, etc.)
#[test]
// When using "should panic" it's suggested you look for specific errors
#[should_panic(expected = "called at least")] 
fn min_calls_not_met() {
    let mut mock = MockCustomTrait::new();
    let method = mock.method_get_int()
        .called_at_least(10)
        .return_result_of(|| 10);
    mock.set_foo(method);

    for _ in 0..9 {
        mock.get_int();
    }
}

#[test]
fn called_once() {
    let mut mock = MockCustomTrait::new();
    let method = mock.method_get_int()
        .called_once()
        .return_result_of(|| 10);
    mock.set_foo(method);

    mock.get_int(); // Commenting this line out would trigger a failure
    // mock.get_int(); // This would trigger a failure
}

外部函数

截至mock_derive 0.6.1,您现在可以模拟静态外部函数。它们与特质模拟共享相同的API。请查看tests/src/foriegn_functions.rs以获取更多示例。

use mock_derive::mock;

// In #[cfg(test)], this will generate functions named 'c_double', 'c_div', etc that you can control
// the behavior of. When not in #[cfg(test)], #[mock] is a noop, meaning that no overhead is added,
// and your program behaves as normal.
#[mock]
extern "C" {
    pub fn c_double(x: isize) -> isize;
    pub fn c_div(x: isize, y: isize) -> isize;
    fn side_effect_fn(x: usize, y: usize);
    fn no_args_no_ret();
}

#[mock]
extern "Rust" {
    fn x_double(x: isize) -> isize;
}

#[test]
fn extern_c_test() {
    let mock = ExternCMocks::method_c_double()
        .first_call()
        .set_result(2);
    
    ExternCMocks::set_c_double(mock);
    unsafe { assert!(c_double(1) == 2); }
}

#[test]
fn extern_rust_test() {
    let mock = ExternRustMocks::method_x_double()
        .first_call()
        .set_result(2);

    ExternRustMocks::set_x_double(mock);
    unsafe { assert!(x_double(1) == 2) };
}

泛型

截至mock_derive 0.5.0,我们支持泛型(基本支持)。请查看tests/src/generics.rs以获取更多示例。

#[mock]
trait GenericTrait<T, U>
      where T: Clone {
      fn merge(&self, t: T, u: U) -> U;
}

#[test]
fn generic_test_one() {
    let mut mock = MockGenericTrait::<f32, i32>::new();
    let method = mock.method_merge()
        .called_once()
        .set_result(30);

    mock.set_merge(method);
    assert!(mock.merge(15.0, 15) == 30);
}

#[mock]
trait LifetimeTrait<'a, T>
    where T: 'a {
    fn return_value(&self, t: T) -> &'a T;
}

static TEST_FLOAT: f32 = 1.0;

#[test]
fn generics_and_lifetime() {
    let mut mock = MockLifetimeTrait::<'static, f32>::new();
    let method = mock.method_return_value()
        .called_once()
        .set_result(&TEST_FLOAT);

    mock.set_return_value(method);
    assert!(mock.return_value(TEST_FLOAT.clone()) == &TEST_FLOAT);
}


测试

在tests/目录中有些测试同时作为示例。进入该目录并运行cargo test

贡献

任何人都可以贡献!如果您有想要贡献的添加/错误修复,只需打开一个PR,它将会被审查。欢迎未完成的PR。只需在PR的名称中包含[WIP]。

许可证

Mock_Derive遵循MIT许可证。

依赖项

~1.5MB
~35K SLoC