Mocking library for Rust.
For now it is not a full-featured mocking library, but just a prototype to gather feedback. For example, only methods with two or less arguments are supported, non-'static lifetimes are not supported and so on.
Mocking magic is implemented using compiler plugin, so nightly Rust is required. It was tested to work with 1.9.0-nightly (924da295c 2016-04-10).
First of all, you must use nighly Rust:
sh
$ multirust override nighly
Add mockers
and mockers_macros
as dependencies to your Cargo.toml
:
```toml [dependencies] mockers = "0.2.1"
[dev-dependencies] mockers_macros = "0.2.1" ```
Say we have air
crate with some trait and method using this trait:
```rust // src/lib.rs
pub trait AirConditioner {
// Note that make_*
methods receive &self
// and not &mut self
as they should. This is due
// to current limitation. It it not inherent and will
// be lifted in the future.
fn makehotter(&self, by: i16);
fn makecooler(&self, by: i16);
fn get_temperature(&self) -> i16;
}
pub fn settemperature20(cond: &airconditioner) { let t = cond.gettemperature(); if t < 20 { cond.makehotter(20 + t); } else { cond.make_cooler(t - 20); } } ```
Import mockers
crate and mockers_macros
compiler plugin into test crate:
```rust // tests/lib.rs
extern crate air; extern crate mockers; ```
Now create mock type for some trait:
rust
mock!{
AirConditionerMock,
air, // This is mocked trait's package.
// Use `self` to mock local trait.
trait AirConditioner {
fn make_hotter(&self, by: i16);
fn make_cooler(&self, by: i16);
fn get_temperature(&self) -> i16;
}
}
Note that you have to duplicate trait definition inside mock!
macro. This is because compiler plugins work at code AST level
and can't get trait information by it's name.
It is all ready now, lets write test:
```rust use mockers::Scenario;
fn testmakehotter() {
let mut scenario = Scenario::new();
let cond = scenario.createmock::
Run tests:
``` $ cargo test … running 1 test test testmakehotter ... FAILED
failures:
---- testmakehotter stdout ----
thread 'testmakehotter' panicked at 'Unexpected call of get_temperature
, no calls are expected', /Users/kriomant/Dropbox/Projects/mockers/src/lib.rs:254
note: Run with RUST_BACKTRACE=1
for a backtrace.
failures: testmakehotter
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
error test failed
```
Seems like call to get_temperature
was not planned by scenario.
Lets start writing scenario:
```rust
fn testmakehotter() {
let mut scenario = Scenario::new();
let cond = scenario.create_mock::
// Expect that conditioner will be asked for temperature
// and return 16.
scenario.expect(cond.get_temperature_call().and_return(16));
// Expect temperature will be set higher by 4.
// Event `()` result must be specified explicitly currently.
scenario.expect(cond.make_hotter_call(4).and_return(()));
mocked::set_temperature_20(&cond);
} ```
Note that we used _call
suffix when specifying expected method calls.
Start tests again:
…
---- test_make_hotter stdout ----
thread 'test_make_hotter' panicked at 'called `Result::unwrap()` on an `Err` value: "36 is not equal to 4"', ../src/libcore/result.rs:746
…
Oops, seems we have a bug in set_temperature_20
function. Fix it and test again:
``` … running 1 test test testmakehotter ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured … ```
*_call
mock methods receive argument matchers as parameters. Any value
of required type (which implements Eq
) can be used as matcher,
in this case it is just compared to actual argument value.
You can use arg!
macro to match argument values against pattern:
```rust
… scenario.expect(mock.methodreceivingoptioncall(arg!(Some())).and_return(())); ```
Basic matchers (comparison: eq
, ne
, gt
, …; logical: not
, or
, and
)
are available in matchers
module.
Specialized matchers (like is_empty
or contains
) will be available soon.
You may easily create ad-hoc matchers using check
method:
rust
use mockers::matchers::check;
scenario.expect(mock.method(check(|x: &Option<u32>| x.is_some()))
.and_return(()));
Unfortunately, you can't create polymorphic checker this way (i.e. checker
which may be used for Option<T>
for any T
). You have to write own
struct and MatchArg
implementation in order to do that.
Use check!
macro instead of check
function to get slightly more
informative error message:
```rust
extern crate mockers;
scenario.expect(mock.method(check!(|x: &Option
Copyright © 2016 Mikhail Trishchenkov
Distributed under the MIT License.