Crates.io | mimicry |
lib.rs | mimicry |
version | 0.1.0 |
source | src |
created_at | 2022-07-04 07:29:17.72266 |
updated_at | 2022-07-04 07:29:17.72266 |
description | Lightweight mocking / spying library |
homepage | |
repository | https://github.com/slowli/mimicry |
max_upload_size | |
id | 618814 |
size | 75,387 |
Mocking in Rust is somewhat hard compared to object-oriented languages. Since there is no implicit / all-encompassing class hierarchy, Liskov substitution principle does not apply, thus making it generally impossible to replace an object with its mock. A switch is only possible if the object consumer explicitly opts in via parametric polymorphism or dynamic dispatch.
What do? Instead of trying to emulate mocking approaches from the object-oriented world,
this crate opts in for another approach, somewhat similar to remote derive from serde
.
Mocking is performed on function / method level, with each function conditionally proxied
to a mock that has access to function args and can do whatever: call the "real" function
(e.g., to spy on responses), maybe with different args and/or after mutating args;
substitute with a mock response, etc. Naturally, mock logic
can be stateful (e.g., determine a response from the predefined list; record responses
for spied functions etc.)
Add this to your Crate.toml
:
[dev-dependencies]
mimicry = "0.1.0"
Example of usage:
use mimicry::{mock, CallReal, Mock, Mut};
// Tested function
#[mock(using = "SearchMock")]
fn search(haystack: &str, needle: char) -> Option<usize> {
haystack.chars().position(|ch| ch == needle)
}
// Mock logic
#[derive(Default, Mock)]
#[mock(mut)]
// ^ Indicates that the mock state is wrapped in a wrapper with
// internal mutability.
struct SearchMock {
called_times: usize,
}
impl SearchMock {
// Implementation of mocked function, which the mocked function
// will delegate to if the mock is set.
fn search(
this: &Mut<Self>,
haystack: &str,
needle: char,
) -> Option<usize> {
this.borrow().called_times += 1;
if haystack == "test" {
Some(42)
} else {
let new_needle = if needle == '?' { 'e' } else { needle };
this.call_real(|| search(haystack, new_needle))
}
}
}
// Test code.
let guard = SearchMock::default().set_as_mock();
assert_eq!(search("test", '?'), Some(42));
assert_eq!(search("needle?", '?'), Some(1));
assert_eq!(search("needle?", 'd'), Some(3));
let recovered = guard.into_inner();
assert_eq!(recovered.called_times, 3);
See crate docs for more details and examples of usage.
'static
type params), functions returning non-'static
responses
and responses with dependent lifetimes, such as in fn(&str) -> &str
, functions with
impl Trait
args etc.impl
blocks, including trait implementations.impl
block may have different
mock states.match
statements.
As a downside, if matching logic needs to be customized across tests, it's (mostly)
up to the test writer.mockall
, simulacrum
, mocktopus
, mockiato
etc. provide more traditional approach
to mocking based on configuring expectations for called functions / methods.
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in mimicry
by you, as defined in the Apache-2.0 license,
shall be dual licensed as above, without any additional terms or conditions.