Giter VIP home page Giter VIP logo

mock_derive's Introduction

Follow us out on Crates.io Build Status

Mock_Derive is an easy to setup, rich mocking library for the Rust programming language. It will allow you to quickly set up unit tests when leveraged with another testing system, like cargo test.

In order to install, just add this line to your Cargo.toml

[dependencies]
mock_derive = "0.8.0"

As a friendly note, mock_derive is not yet a 1.0 crate, and is still under heavy development. As such, you may find several real world use cases that are not yet supported. If you find such a case, please open an issue and we will look at it as soon as possible.

mock_dervice was developed in 2017, however due to the life circumstance of the primary contributor, development was paused. In 2020, development has resumed.

How mock_derive is different to previous mocking libraries in other languages.

In traditional OO languages, mocking is usually based around inheritance, or a mix of method replacement in more dynamic languages. You make a Foo from a mock factory, define the behavior of that Foo, and pass it to functions expecting a Foo. Rust does not have traditional inheritance, meaning that only a Foo is a Foo. Mock_Derive encourages Implementation Mocking. This means that you will derive your mock for a trait. You will pass that mock to methods expecting something that implements that trait, and you will be able to control the behavior of that mock, similar to other mocking libs you may have worked with in the past.

Examples

See src/examples for working examples

Using this crate looks something like this:

#![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
    }
}

You'll notice that we have included a #[mock] directive above our trait definition. By default, this will generate an implementation of CustomTrait named "MockCustomTrait", that has helper functions used to control its behavior. For example, we can write the following test functions:

#[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
}

EXTERN FUNCTIONS

As of mock_derive 0.6.1, you can now mock static external functions. They share the same API as trait mocks. Check out tests/src/foriegn_functions.rs for more examples.

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) };
}

GENERICS

As of mock_derive 0.5.0, we have (basic) support for generics. Check out tests/src/generics.rs for more examples.

#[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);
}

TESTING

There are some tests which double as examples in the tests/ directory. cd into that directory and run cargo test.

CONTRIBUTING

Anyone is welcome to contribute! If you have an addition/bug fix that you would like to contribute, just open a PR and it will be looked at. Work in Progress (WIP) PRs are also welcome. Just include [WIP] in the name of the PR.

LICENSE

Mock_Derive is licensed under MIT.

mock_derive's People

Contributors

bbigras avatar daviddesimone avatar wilfred avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mock_derive's Issues

Support dyn Trait syntax

Edition Guide Documentation

In Rust 2018, trait objects are going to be marked explicitly by writing dyn in front of them. #[mock] currently panics when encountering this syntax:

trait Foo {}

#[cfg_attr(test, mock)]
pub trait Bar {
    fn foo(foo: &dyn Foo);
}

Compiling this code results in the following error message.

error: custom attribute panicked
  --> environment/src/properties.rs:35:18
   |
35 | #[cfg_attr(test, mock)]
   |                  ^^^^^
   |
   = help: message: called `Result::unwrap()` on an `Err` value: "failed to parse item: \"pub trait Bar {\\n    fn foo(foo: &dyn Foo);\\n}\""

support mocking inherited Traits

Attempting to use #[mock] on a derived Trait will fail, regardless of whether or not the base Trait is also #[mock]ed. For example:

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

use mock_derive::mock;

// Whether or not we mock Base, compiling Derived will fail
#[mock]
pub trait Base {
}

#[mock]
pub trait Derived : Base {
}

results in:

> rustup run nightly cargo test
   Compiling mockderive_inherited v0.1.0 (file:///usr/home/somers/src/rust/mockderive_inherited)
error[E0277]: the trait bound `MockDerived: Base` is not satisfied
  --> src/lib.rs:11:1
   |
11 | #[mock]
   | -------
   | |
   | the trait `Base` is not implemented for `MockDerived`
   | in this macro invocation

error: aborting due to previous error

error: Could not compile `mockderive_inherited`.
warning: build failed, waiting for other jobs to finish...
error: build failed

Let mocks implement std::fmt::Debug

It is very common to trait bind your trait to Debug:

pub trait Object: Debug {
    // ...
}

#[mock] fails here however, as the resulting mock does not implement std::fmt::Debug:

   | #[cfg_attr(test, mock)]
   |                  -----
   |                  |
   |                  `properties::MockObject` cannot be formatted using `{:?}`
   |                  in this macro invocation
   |
   = help: the trait `std::fmt::Debug` is not implemented for `properties::MockObject`
   = note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`

I propose simply adding a #[derive(Debug)] to the generated mock.

Fails to build with rust 1.30.0

Mock_derive fails to build with nightly 1.30.0. I haven't tried with nightly 1.29.0, but I suspect it will fail too.

cargo +nightly-2018-09-06-x86_64-unknown-freebsd test
   Compiling unicode-xid v0.0.4
   Compiling quote v0.3.15
   Compiling lazy_static v0.2.11
   Compiling synom v0.11.3
   Compiling syn v0.11.11
   Compiling mock_derive v0.7.0 (file:///usr/home/somers/src/rust/mock_derive/mock_derive)
warning: the feature `proc_macro` has been stable since 1.29.0 and no longer requires an attribute to enable
  --> src/lib.rs:25:12
   |
25 | #![feature(proc_macro)]
   |            ^^^^^^^^^^
   |
   = note: #[warn(stable_features)] on by default

warning: the feature `proc_macro` has been stable since 1.29.0 and no longer requires an attribute to enable
  --> src/lib.rs:25:12
   |
25 | #![feature(proc_macro)]
   |            ^^^^^^^^^^
   |
   = note: #[warn(stable_features)] on by default

error[E0658]: procedural macros cannot expand to macro definitions (see issue #38356)
 --> examples/generics.rs:6:1
  |
6 | #[mock]
  | ^^^^^^^
  |
  = help: add #![feature(proc_macro_gen)] to the crate attributes to enable

error[E0658]: procedural macros cannot expand to macro definitions (see issue #38356)
 --> examples/simple.rs:6:1
  |
6 | #[mock]
  | ^^^^^^^
  |
  = help: add #![feature(proc_macro_gen)] to the crate attributes to enable

error[E0658]: procedural macros cannot expand to macro definitions (see issue #38356)
 --> examples/extern_functions.rs:9:1
  |
9 | #[mock]
  | ^^^^^^^
  |
  = help: add #![feature(proc_macro_gen)] to the crate attributes to enable

error[E0658]: procedural macros cannot expand to macro definitions (see issue #38356)
  --> examples/generics.rs:12:1
   |
12 | #[mock]
   | ^^^^^^^
   |
   = help: add #![feature(proc_macro_gen)] to the crate attributes to enable

error: aborting due to previous error
error: aborting due to 2 previous errors


For more information about this error, try `rustc --explain E0658`.
For more information about this error, try `rustc --explain E0658`.
error: Could not compile `mock_derive`.
warning: build failed, waiting for other jobs to finish...
error: Could not compile `mock_derive`.
warning: build failed, waiting for other jobs to finish...
error[E0658]: procedural macros cannot expand to macro definitions (see issue #38356)
  --> examples/extern_functions.rs:17:1
   |
17 | #[mock]
   | ^^^^^^^
   |
   = help: add #![feature(proc_macro_gen)] to the crate attributes to enable

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0658`.
error: Could not compile `mock_derive`.

To learn more, run the command again with --verbose.

Support for building on stable+nightly

Is it possible to enable this crate while still being able to build using stable?

i.e. run tests which can be run on stable, then run all tests (including mocked tests) on nightly

Cannot mock Send traits

mock_derive cannot mock traits that are Send. Example:

    fn send() {
        #[mock]
        pub trait A {
            fn foo(&self);
        }

        let mock = MockA::new();
        let _ = Box::new(mock) as Box<A + Send>;
    }

Gives this error:

error[E0277]: `(dyn std::ops::FnMut() + 'static)` cannot be sent between threads safely
   --> src/t_mock_derive.rs:251:17
    |
251 |         let _ = Box::new(mock) as Box<A + Send>;
    |                 ^^^^^^^^^^^^^^ `(dyn std::ops::FnMut() + 'static)` cannot be sent between threads safely                                                    
    |
    = help: the trait `std::marker::Send` is not implemented for `(dyn std::ops::FnMut() + 'static)`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<(dyn std::ops::FnMut() + 'static)>`
    = note: required because it appears within the type `std::boxed::Box<(dyn std::ops::FnMut() + 'static)>`
    = note: required because it appears within the type `std::option::Option<std::boxed::Box<(dyn std::ops::FnMut() + 'static)>>`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<std::option::Option<std::boxed::Box<(dyn std::ops::FnMut() + 'static)>>>`
    = note: required because it appears within the type `<t_mock_derive::t::MockDerive as TestSuite>::send::MockMethodForA<()>`
    = note: required because it appears within the type `std::option::Option<<t_mock_derive::t::MockDerive as TestSuite>::send::MockMethodForA<()>>`
    = note: required because it appears within the type `<t_mock_derive::t::MockDerive as TestSuite>::send::MockA`
    = note: required for the cast to the object type `dyn <t_mock_derive::t::MockDerive as TestSuite>::send::A + std::marker::Send`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.