codesandwich / mocktopus Goto Github PK
View Code? Open in Web Editor NEWMocking framework for Rust
License: MIT License
Mocking framework for Rust
License: MIT License
This code compiles causing conversion of &str
to &'static str
:
#[mockable]
fn terrible(_: &str) -> &'static str {
"a"
}
#[test]
fn terrible_test() {
// This closure has type `for <'a> fn(&'a str) -> MockResult<(&'a str,), &'a str>`
terrible.mock_safe(|a| MockResult::Return(a));
let x = "abc".to_string();
let y = x.as_str();
let z: &'static str = terrible(y);
assert_eq!("abc", z);
}
Hey,
what do you think of adding a completly new API to improve readability of mockings. I would suggest something like this:
when(namespace::function)
.expected(times(3))
.called_with(
is(equal_to(5)),
is(none())
)
.returns(7);
A call to expected
would be optional and defaulting to times(1)
. Other parameter values could be at(2)
, al_least(3)
etc.
called_with
would be a method accepting a hamcrest2 matcher for each parameter. Internal we could simply test these with assert_that!(expected_value, <matcher>)
. This method call would also be optional, defaulting to empty parameter list.
returns
will simply specify, what the function returns. Alternatively runs_original()
will delegate the call to the original implementation.
The only thing what I need to call this a full mocking library would be the ability to mock the behavior for struct instances, like:
struct Struct(u8);
#[mockable]
impl Struct {
fn get(&self) -> u8 {
self.0
}
}
fn test() {
let a = Struct(1);
let b = Struct(2);
when(Struct::get).on(&a).returns(10);
assert_that!(a.get(), is(equal_to(10)));
assert_that!(b.get(), is(equal_to(2)));
}
It would be a nice feature to be able to configure (via parameter, function call, etc) if calls not specified in expected
raise an error and let the test fail, like:
#[mockable]
fn function(a: u32) -> u32 {
a
}
#[test]
fn test() {
// order of mocks matter: at first call param is 1, at second param is 2
when(function)
.called_with(is(equal_to(1)))
.returns(5);
when(function)
.called_with(is(equal_to(2)))
.returns(7);
// something like this, to specify mock type
fail_on_unmocked_calls(function);
assert_that!(function(1), is(equal_to(5)));
assert_that!(function(2), is(equal_to(7)));
// following call should let the tast fail
assert_that!(function(3), is(equal_to(3)));
}
For struct instances, you could specify with anything like the following, where the test would fail on any call to a method of that struct instance.
let a = Struct(1);
fail_on_unmocked_calls(&a);
These are just some ideas inspired by Mockito, PHPUnit and similar mocking libraries. What do you think of it? Would it be something you want to add in this crate?
I would love to help and contribute to your project if so.
Yes please.
Mocks taking effect in every thread during test runtime. Probably should block other test runs, preferably only multithreaded
Hi! I'm new to Mocktopus.
I found that I can't mock impl Future
functions due to type mismatch.
My code:
#[cfg(test)]
use mocktopus::macros::*;
use mocktopus::mocking::*;
use futures::prelude::*;
use futures::{future, Future};
use futures::executor::{block_on};
fn main() {
println!("Hello, world!");
}
#[mockable]
pub fn future() -> impl Future<Output=i32> {
future::ready(1)
}
#[test]
fn mock_test() {
future.mock_safe(|| MockResult::Return(future::ready(2)));
assert_eq!(2, block_on(future()));
}
dependencies in Cargo.toml:
[dependencies]
futures = "0.3"
[dev-dependencies]
mocktopus = "0.7.0"
toolchain:
nightly-x86_64-apple-darwin unchanged - rustc 1.41.0-nightly (797fd9262 2019-11-26)
error:
error[E0308]: mismatched types
--> src/main.rs:20:44
|
20 | future.mock_safe(|| MockResult::Return(future::ready(2)));
| ^^^^^^^^^^^^^^^^ expected opaque type, found struct `futures_util::future::ready::Ready`
|
= note: expected opaque type `impl core::future::future::Future`
found struct `futures_util::future::ready::Ready<{integer}>`
error: aborting due to previous error
Any ideas?
Line 10 in e3b533d
This link leads to a page with a banner "mocktopus_macros-0.7.5 doesn't have any documentation." Probably is was meant to point to something else.
get_type_id
has been stabilized and renamed to type_id
in Rust 1.33.0. As a result, Mocktopus no longer builds with a nightly compiler as of 2019-01-25. rust-lang/rust#27745
First new version of syn is needed, which uses proc-macro2 0.4: dtolnay/syn#427
Trying to mock an unsafe function results in an error. Is there any particular reason my unsafe function cannot be mocked?
Minimal test:
unsafe fn count() -> i32 {
1
}
#[test]
fn unsafe_test() {
unsafe {
count.mock_raw(|| MockResult::Return(0));
assert_eq!(0, count());
}
}
Output:
error[E0599]: no method named `mock_raw` found for type `unsafe fn() -> i32 {test::count}` in the current scope
--> src\example.rs:10:23
|
| count.mock_raw(|| MockResult::Return(0));
| ^^^^^^^^
|
= note: count is a function, perhaps you wish to call it
= note: the method `mock_raw` exists but the following trait bounds were not satisfied:
`unsafe fn() -> i32 {test::count} : mocktopus::mocking::Mockable<_, _>`
I do not believe there is a way to verify that all mocks were actually called. Currently the maximum bound is automatically enforced, but the minimum bound is not possible.
I can't store this state outside of the mock closure, since the mock closure is Fn
, not FnMut
. Where should it be stored?
Mocktopus' documentation on doc.rs is failing with:
docs.rs failed to build mocktopus-0.5.0
Please check build logs and if you believe this is docs.rs' fault, report into this issue report.
Doc.rs build logs
Looking at the build logs, it is as if the documentation was built with stable rustc.
Generally speaking, safe operations have no suffix, while their unsafe counterparts have _unchecked
, _unsafe
, or _raw
like mock_raw
does. So, I think it would be more ergonomic to switch all mock_safe
to mock
for the next breaking version bump.
Mocktopus is very handy for mocking free functions. But when mocking structs, it requires a real copy of the struct to be instantiated. That's not always easy or possible in test environments. That's why most mocking libraries work in terms of traits instead. It would be great if Mocktopus could provide a macro that would instantiate a trait and mock all of its methods. Something like this:
#[mockable]
pub trait A {
fn foo(&self) -> u32;
}
mock!{AMock, A}
The mock
macro could expand to this:
#[derive(Default)]
pub struct AMock {}
#[mockable]
impl AMock {
fn foo(&self) -> u32 {
unimplemented!()
}
}
Example:
#[mockable]
fn function(_: &String) -> &'static str {
"not mocked"
}
#[test]
fn horrible_things() {
let input = "mocked".to_string();
function.mock_safe(|i| MockResult::Return(i.as_str()));
let result: &'static str = function(&input);
assert_eq!("mocked", result);
}
Take the following example:
#[mockable]
fn call_with_closure(
num: u32,
math_op: impl FnOnce(u32) -> u32,
) -> u32 {
math_op(num)
}
This fails with the following compilation error:
error[E0641]: cannot cast to a pointer of an unknown kind
--> src/main.rs:21:10
|
21 | ) -> u32 {
| __________^
22 | | math_op(num)
23 | | }
| |_^ needs more type information
Add feature of injecting all modules in a crate with a single line of code in root module:
#![inject_mocks]
Blocked: rust-lang/rust#41430
Similar to #61, but when the reference parameter is the first parameter I see the same complier error when running tests.
Adding a lifetime works when there is only one reference parameter, but not when there are multiple.
Examples:
// Fails when mockable - error[E0621]: explicit lifetime required in the type of `p1`
// #[cfg_attr(test, mockable)]
pub async fn ref_first(p1: &str) {}
#[cfg_attr(test, mockable)]
pub async fn ref_first_with_lifetime<'a>(p1: &'a str) {}
#[cfg_attr(test, mockable)]
pub async fn ref_second(p1: String, p2: &str) {}
// Fails when mockable - error: custom attribute panicked
// #[cfg_attr(test, mockable)]
pub async fn multi_ref_first_with_lifetime<'a>(p1: &'a str, p2: &'a str) {}
#[cfg_attr(test, mockable)]
pub async fn multi_ref_second(p1: String, p2: &str, p3: &str) {}
Using mocktopus = "0.7.10"
I have followed every instructions given in rust docs (https://docs.rs/mocktopus/0.7.5/mocktopus), but I get the following error:
Please help
#[mockable]
mod module;
Doesn't make content of module
mockable since rustc 1.30.0-nightly (63d51e89a 2018-09-28). The necessary functionality was removed from rustc, it may or may not be restored: rust-lang/rust#54727.
Currently when compiler finds an error in mock injected code, it prints a message pointing to the injection annotation:
error[E0106]: missing lifetime specifier
--> tests/mocking_regular_functions.rs:6:1
|
6 | #[inject_mocks]
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
= help: consider giving it a 'static lifetime
Expected behavior of compiler is pointing to place, where the error actually occurs.
Blocked until Syn 0.12: dtolnay/syn#178
I have this minimal example:
#[cfg(test)]
use mocktopus::macros::*;
pub struct Foo { }
#[cfg_attr(test, mockable)]
impl Foo {
pub async fn read(&mut self, buf: &mut[u8]) {
let _ = buf;
}
}
It builds fine without tests, but fails with tests enabled with:
error[E0621]: explicit lifetime required in the type of `buf`
--> src/main.rs:13:5
|
12 | pub async fn read(&mut self, buf: &mut[u8]) -> ()
| -------- help: add explicit lifetime `'mocktopus` to the type of `buf`: `&'mocktopus mut [u8]`
13 | / {
14 | | let _ = buf;
15 | | }
| |_____^ lifetime `'mocktopus` required
Using pub async fn read<'a>(&mut self, buf: &'a mut[u8])
instead silences the error. I have no idea if it's a good workaround.
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2089:50
|
2089 | let mut existing_history = match self.load_history_from_file(ctx).compat().await {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2094:47
|
2094 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2107:64
|
2107 | let total_amount = u256_to_big_decimal(amount, self.decimals).unwrap();
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2114:33
|
2114 | if from_addr == self.my_address {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2118:31
|
2118 | if to_addr == self.my_address {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2123:31
|
2123 | "coin" => self.ticker.clone(), "client" => "ethereum", "method" => "tx_detail_by_hash");
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2125:37
|
2125 | let web3_tx = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2136:47
|
2136 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2148:31
|
2148 | "coin" => self.ticker.clone(), "client" => "ethereum", "method" => "tx_detail_by_hash");
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2155:47
|
2155 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2162:37
|
2162 | let receipt = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2173:47
|
2173 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2183:39
|
2183 | let fee_coin = match &self.coin_type {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2184:41
|
2184 | EthCoinType::Eth => self.ticker(),
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2195:35
|
2195 | let block = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2206:47
|
2206 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2214:47
|
2214 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2229:27
|
2229 | coin: self.ticker.clone(),
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2249:33
|
2249 | if let Err(e) = self.save_history_to_file(ctx, existing_history).compat().await {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2252:43
|
2252 | &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2481:83
|
2481 | mm_counter!(ctx.metrics, "tx.history.request.count", 1, "coin" => self.ticker.clone(), "method" => "tx_detail_by_hash");
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2483:37
|
2483 | let web3_tx = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2494:47
|
2494 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2509:47
|
2509 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2516:84
|
2516 | mm_counter!(ctx.metrics, "tx.history.response.count", 1, "coin" => self.ticker.clone(), "method" => "tx_detail_by_hash");
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2518:37
|
2518 | let receipt = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2529:47
|
2529 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2539:39
|
2539 | let fee_coin = match &self.coin_type {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2540:41
|
2540 | EthCoinType::Eth => self.ticker(),
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2555:38
|
2555 | if call_data.from == self.my_address {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2565:36
|
2565 | if call_data.to == self.my_address {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2573:35
|
2573 | let block = match self
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2584:47
|
2584 | ... &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2598:27
|
2598 | coin: self.ticker.clone(),
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2619:33
|
2619 | if let Err(e) = self.save_history_to_file(ctx, existing_history.clone()).compat().await {
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0434]: can't capture dynamic environment in a fn item
--> mm2src/coins/eth.rs:2622:43
|
2622 | &[&"tx_history", &self.ticker],
| ^^^^
|
= help: use the `|| { ... }` closure form instead
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:1964:76
|
1964 | BlockNumber::Number((saved_events.earliest_block - 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:1977:25
|
1977 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:1988:76
|
1988 | BlockNumber::Number((saved_events.earliest_block - 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:2001:25
|
2001 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:2026:74
|
2026 | BlockNumber::Number((saved_events.latest_block + 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:2040:25
|
2040 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:2050:74
|
2050 | BlockNumber::Number((saved_events.latest_block + 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:2064:25
|
2064 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:2414:74
|
2414 | BlockNumber::Number((saved_traces.latest_block + 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:2428:25
|
2428 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error[E0698]: type inside `async fn` body must be known in this context
--> mm2src/coins/eth.rs:2437:74
|
2437 | BlockNumber::Number((saved_traces.latest_block + 1).into()),
| ^ cannot infer type for type `{integer}`
|
note: the type is part of the `async fn` body because of this `await`
--> mm2src/coins/eth.rs:2451:25
|
2451 | Timer::sleep(10.).await;
| ^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 42 previous errors
Some errors have detailed explanations: E0434, E0698.
For more information about an error, try `rustc --explain E0434`.
error: could not compile `coins`
To learn more, run the command again with --verbose.
The new version seems to break every existing implementation - furthermore, it's force user to change existing code to suits the mockable behaviour, this is wrong IMHO.
(If I move those 2 function into another impl block that doesn't have the macros mockable that make everything compile, but I thought you would like to know that for some complex block there is some problem)
No idea, how to do it, compiler plugin passed to rustc maybe?
There seems to be a regression with latest nightly.
$> cargo test
[...]
Compiling mocktopus v0.7.11
error[E0059]: type parameter to bare `FnOnce` trait must be a tuple
--> /home/tobhap/.cargo/registry/src/github.com-1ecc6299db9ec823/mocktopus-0.7.11/src/mocking.rs:100:25
|
100 | impl<T, O, F: FnOnce<T, Output = O>> Mockable<T, O> for F {
| ^^^^^^^^^^ the trait `Tuple` is not implemented for `T`
|
note: required by a bound in `FnOnce`
help: consider restricting type parameter `T`
|
100 | impl<T: std::marker::Tuple, O, F: FnOnce<T, Output = O>> Mockable<T, O> for F {
| ++++++++++++++++++++
error[E0059]: type parameter to bare `FnMut` trait must be a tuple
--> /home/tobhap/.cargo/registry/src/github.com-1ecc6299db9ec823/mocktopus-0.7.11/src/mocking.rs:192:12
|
192 | M: FnMut<I, Output = MockResult<I, O>> + 'a,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Tuple` is not implemented for `I`
|
note: required by a bound in `FnMut`
help: consider further restricting type parameter `I`
|
192 | M: FnMut<I, Output = MockResult<I, O>> + 'a, I: std::marker::Tuple
| ~~~~~~~~~~~~~~~~~~~~~~~
error[E0059]: type parameter to bare `FnMut` trait must be a tuple
--> /home/tobhap/.cargo/registry/src/github.com-1ecc6299db9ec823/mocktopus-0.7.11/src/mocking.rs:204:12
|
204 | M: FnMut<I, Output = MockResult<I, O>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Tuple` is not implemented for `I`
|
note: required by a bound in `FnMut`
help: consider further restricting type parameter `I`
|
204 | M: FnMut<I, Output = MockResult<I, O>>, I: std::marker::Tuple
| ~~~~~~~~~~~~~~~~~~~~~~~
error[E0059]: type parameter to bare `FnMut` trait must be a tuple
--> /home/tobhap/.cargo/registry/src/github.com-1ecc6299db9ec823/mocktopus-0.7.11/src/mocking.rs:45:27
|
45 | unsafe fn mock_raw<M: FnMut<T, Output = MockResult<T, O>>>(&self, mock: M);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Tuple` is not implemented for `T`
|
note: required by a bound in `FnMut`
help: consider restricting type parameter `T`
|
11 | pub trait Mockable<T: std::marker::Tuple, O> {
| ++++++++++++++++++++
error[E0059]: type parameter to bare `FnMut` trait must be a tuple
--> /home/tobhap/.cargo/registry/src/github.com-1ecc6299db9ec823/mocktopus-0.7.11/src/mocking.rs:65:21
|
65 | fn mock_safe<M: FnMut<T, Output = MockResult<T, O>> + 'static>(&self, mock: M);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Tuple` is not implemented for `T`
|
note: required by a bound in `FnMut`
help: consider restricting type parameter `T`
|
11 | pub trait Mockable<T: std::marker::Tuple, O> {
| ++++++++++++++++++++
For more information about this error, try `rustc --explain E0059`.
error: could not compile `mocktopus` due to 5 previous errors
warning: build failed, waiting for other jobs to finish...
$> rustc --version
rustc 1.67.0-nightly (7eef946fc 2022-11-06)
In-code Rust docs, explanatory comments, readme.md
Mutable mock closure can cause UB when it calls recursively the mocked function. Such call makes the same mutable closure being run at the same time with mutable access to its variables. That's enough to cause an UB:
First off let me say thanks! This is an awesome crate and badly needed. I got it working with mocking a single function that that works great. However I'm a bit confused why it doesn't work when I try to mock out multiple functions. Example: https://github.com/Comcast/Bynar/blob/state-machine/src/test_disk.rs#L209. rust seems to ignore the extra mocks and I'm not sure why.
Hi, for function to test like following:
fn handle_response(response:Response) {
save_db(response.get_users());
let unread_count = read_unread_from_db();
send_push_notification(unread_count);
}
I'd like to make sure send_push_notification did get called with the desired count in the unit test. How could I do this?
Thanks a lot!
mod foo;
injection not workingBox::leak
, Option::unwrap
, Vec::remove(0)
Remove lifetime remover
Lifetime remover is a big boilerplate and actually a dirty hack.
The lifetime remover is needed in cases, where Rust can't reason well enough about lifetimes of generics.
Example:
trait Trait<T> {
fn method() -> &'static str {
"method"
}
fn another() -> &'static str;
}
struct Struct();
impl<'a, T> Trait<&'a T> for Struct {
fn another() -> &'static str {
//<Self as Trait<&'a T>>::method() // FAILS TO COMPILE
<Self as Trait<&T>>::method() // COMPILES FINE
}
}
This is exactly the behavior that is needed in code generated in Mocktopus. It builds full UFC call to trait method of struct with exactly same generics params. Right now copying trait name from impl header Trait<&'a T>
and putting it in <Self as Trait<&'a T>>::method()
confuses the compiler, it can't find out, that &'a T
implies T: 'a
and complains, that T
may not live as long as 'a
. The workaround is to remove lifetimes from generic parameters and make it <Self as Trait<&T>>::method()
. It works, because lifetimes are actually dropped during monomorphisation and Rust can prove this statement to be safe.
If I understand correctly, rust-lang/rust#44493 solves exactly the hole in Rust's lifetime reasoning, which forces Mocktopus to use lifetime remover
Add tools for convenient storage and verification of mock state, e.g. call count, list of responses or last call arguments
Is it possible to mock standard library functions?
I tried doing something like this but it didn't seem to work:
#[test]
fn mock_current_dir() {
env::current_dir.mock_safe(|| MockResult::Return(Ok(PathBuf::from("/foo/bar"))));
let dir = env::current_dir().unwrap();
assert_eq!(dir, PathBuf::from("/foo/bar"));
}
nightly-x86_64-unknown-linux-gnu (default)
rustc 1.40.0-nightly (421bd77f4 2019-10-06)
Run cargo test --release
.
all tests pass
running 67 tests
test injecting_fn_with_arg_requiring_drop::when_not_mocked_then_runs_normally ... ok
test injecting_fn_with_arg_requiring_drop::when_mocked_then_returns_mock ... ok
test injecting_fn_with_generic_return_type::when_return_type_has_destructor::and_mocked_then_returns_mock ... ok
test injecting_fn_with_generic_return_type::when_return_type_has_destructor::and_not_mocked_then_runs_normally ... error: process didn't exit successfully: `/home/artem/dev/mock/Mocktopus/target/release/deps/injecting-6b1dde59d7e99d97` (signal: 4, SIGILL: illegal instruction)
The SIGILL here is caused by mem::zeroed
usage which is UB when used with references:
let _x: &i32 = unsafe { mem::zeroed() }; // Undefined behavior!
It seems that taking generators instead of regular functions could be a vast improvement for mocking flexibility and ergonomics. The user could easily create complex sequences of values by yielding them. The problem is that this API is highly experimental so probably it should be feature-gated, at least for now.
Mocktopus closures are FnMut
s, from which nothing can be moved out. That makes the following code impossible:
#[derive(Debug, Eq, PartialEq)]
pub struct UniquelyOwned(u32);
#[mockable]
fn foo() -> UniquelyOwned {UniquelyOwned(0)}
let uo = UniquelyOwned(42);
foo.mock_safe(move || {
MockResult::Return(uo)
});
assert_eq!(UniquelyOwned(42), foo());
There's no way to return a UniquelyOwned
object since it's not Clone
. Mockers solves this problem by providing a different method to return owned values. Mocktopus could do it do, with a method like mock_once
that takes an FnOnce
argument instead of an FnMut
.
I have 2 creates in my project, base and service, and service crate based on base.
And the code is something like this:
crate base {
db.rs
#[mockable]
fun get_users()
}
crate service {
import db;
fun get_users() -> vec<Users>{
let db_users = db.get_users();
if db_users.len() > 0 {
return db_users;
} else {
let users = vec!['a', 'b', 'c']
return users;
}
}
mod test {
import db:get_users()
#[test]
fun test_get_user{
let results = Vec::new();
db.get_users.mock_safe( || MockResult::Return(Ok(results)));
assert_eq(get_users(), vec!['a', 'b', 'c']);
}
}
}
I expect db.get_users() is not called, but mock result returns, but it actually didn't respect the mock and goes to expect the real db.get_users() code.
Does mocktopus support mocking on method in different crate or I made mistakes?
Thanks a lot
The link the documentation on docs.rs is broken. I get a 404 when I'm trying to reach it.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.