mockall
mockall copied to clipboard
Add ability to reset / clear existing expectations?
Hello, thanks so much for this library, super useful.
I have test setup code that will set a bunch of default expectations on mocks, but then some tests would like to alter the default expectations after the mock is built. Right now, if I just make another expect_foo
call, that'll create an additional expectation, where I'd like it to actually replace the existing one. A nice way to do this might be something like a remove_expectations_foo
method. Would this be a reasonable feature to add?
Thanks!
Note that expectations are evaluated in LIFO order. So if you set an expectation in your setup code and another one in a test case, the latter will be evaluated first. That means the former doesn't usually cause any problems, unless there is a .times
on it or something. If you do have problems, I would suggest refactoring your setup strategy so as not to create expectations that you don't expect to fulfill.
@asomers Hmm, the docs say the opposite about ordering: https://docs.rs/mockall/0.9.1/mockall/index.html#matching-multiple-calls ? It appears to be FIFO behavior to me.
And my use case is indeed using .times
. I could change the setup strategy, but it'd be quite a bit more complicated than being able to wipe an existing expectation. Is your concern about having too many methods on the generated mock?
Sorry; yes you're right it uses FIFO order. Have you considered just removing .times
from your setup? In my own projects, the only methods I expect from the setup code are boilerplate stuff that could be called any number of times with the same result. Any method that must have a fixed number of calls I expect from the test method.
It's important to me to verify the number of calls is correct. I happen to have some pretty complex logic involved in setting up the expectations, which is why it's useful to have this common setup that works the majority of the time, but occasionally a test has reason to want to overwrite one of the method's expectations.
So, certainly possible to restructure how they're set up to accommodate that, but I figure some functionality like this is probably a useful tool to have in any case if it's not too much of an imposition on the library.
this is also something that would be useful for us. we're mocking some static methods, which have global expectations. everything works great if all the tests pass. but, one test failing can bleed into another if the test fails prior to all the expectations being hit.
I have the issue of needing to change the return in the middle of a test. Also, it would be great to be able to set a catch all.
For example, if you are taking an index to a collection, you might want most of the output to be the default but only a few to be set. Currently, I don't know of a way to set the default in the setup. This makes my tests messy because I have call a function in the test after setting the other expectations that finalizes the setup with a default as the last expectation for a function.
If I later want to add a new expectation, I have to recreate it from scratch each time I change anything and include everything I did before but with a change.
Some way to overwrite expectations would be wonderful and having a way for a default set early would be awesome too. How about an option to be LIFO instead of FIFO? I think this might solve both.
Well, @MikeColeGuru the straightforward way to have a "default" expectation that is also overridden by a "special case" expectation is to use the matchers, and rely on FIFO ordering. For example,
let mut mock = MockMyCollection::new();
mock.expect_get()
.with(eq(4))
.return_const(Some(5));
mock.expect_get()
.with(any()) // "any" is really just documentation. You could leave this line out
.return_const(None);
In this setup, calling mock.get(4)
will return Some(5)
, but calling it with any other value will return None
. That said, I'm warming up to the idea of editting existing expectations. We just need to decide on what would be the best API. Can you suggest something?
I would love to set the default in the setup.
fn setup() ->Rc<RefCell<MockMyCollection>> {
let mock = Rc::new(RefCell::new(MockMyCollection::new()));
mock.borrow_mut()
.expect_get()
.with(any()) // "any" is really just documentation. You could leave this line out
.return_const(None);
mock
}
#[test]
fn test_something() {
let mock = setup();
mock.borrow_mut()
.expect_get()
.with(eq(4))
.return_const(Some(5));
// do something with it
}
But the above doesn't work because the default is what you get for everything.
So, I end up having to call setup + new expectations many times for each time I change something because any
has to be last and sorta locks it all in place.
I would like the API to be able to overwrite what you set in the normal way you use it.
There might be a remove_expect
after specifying which one you want through the normal methods.
// create it
mock.expect_foo()
.with(eq(5))
.return_const(7);
// edit it
mock.expect_foo()
.with(eq(5))
.return_const(8);
// remove it
mock.expect_foo()
.with(eq(5))
.remove_expect();
Alternatively ...
// create it
mock.expect_foo()
.with(eq(5))
.return_const(7);
// edit it
mock.expect_foo()
.with(eq(5))
.return_const(8);
// remove it
mock.remove_foo()
.with(eq(5))
// remove all expects for foo
mock.remove_foo()
Another API that could make sense would be something like:
let mock = setup();
let expectations = mock.take_expectations();
let updated_expectations = expectations.filter(|e| e.call_func == MyMockTypeCall::Foo);
mock.set_expectations(updated_expectations)
Here take_expectations
returns something like Iterator<Item=MyMockTypeExpectation>
. You could also have an expectations_mut
that gives mut references for editing.
Where a MyMockTypeExpectation
is generated by the mocking code, and looks something like:
struct MyMockTypeExpectation {
matchers: Vec<Box<dyn Predicate>>,
call_type: MyMockTypeCall,
times: Option<usize>,
// etc, I suppose most of this probably already exists in generated Expectation types.
// All that really matters here is exposing that stuff.
}
enum MyMockTypeCall {
Foo,
Bar
}
This lets you manipulate all the existing expectations with as much power as you could ever want. I wouldn't want to have the "edit" style API described in @MikeColeGuru's example because that seems to overlap with the existing FIFO concept of adding new expectations. The remove
APIs make sense to me, though.
I would love to use mocked traits with defaults/fallbacks in conjunction with the rstest crate as a fixture. Currently, this is not really possible because, as discussed in this thread, the expectations are FIFO. This sadly means that having a "catch-all" fixture first would prevent adding more specific expectations for a test later.
This could be solved if we could configure the expectations to be LIFO instead.