Reputation: 60574
I have a quite complicated method for which I want to test the behavior (using Mockito and JUnit). This method takes an object (let's call its type State
) as input, and should take a few different state variables into account for deciding its output.
As an example, considering the following specification (s
is a mock of the State
class):
s.varOne
is set, return its value.s.varTwo
is set, return that instead.s.varThree
is set, call s.update(s.varThree)
and then return s.varOne
, which will now have a value (even though it didn't at stage 1.)In order to test case 3 properly, I would like to set up the s
object so that s.varOne
and s.varTwo
are both unset to begin with, but if (and only if!) the sut calls s.update(s.varThree)
, then after that s.varOne
returns something.
Is there a good way to setup this behavior in Mockito?
I have considered setting up some chain of return values for s.varOne
, and then verifying that the order of the calls corresponds to the order of the outputs (as well as that the return value of the sut is correct, of course), but this feels dirty; if I then change the method to calculate its return value in some other way, which calls s.varOne
a different number of times but doesn't change its output, then the test will fail even though the functionality is the same.
My ideal solution is a way where I can add some "delayed" setup for the mock object, which is run when the sut calls the s.update()
method, but I can't figure out a way to accomplish that.
Upvotes: 5
Views: 11047
Reputation: 95634
You have a couple of options to mock a state change here, a good option and a better option. The best option, as tieTYT notes above, is to just to untangle the general contract of State
: Does it really make sense for State
to be mutable, and to have self-mutating methods that aren't simple setters?
The good option (sometimes) is to create a disposable subclass—or, better, an interface implementation—of State
.
@Test public void yourTest() {
SystemUnderTest sut = createSystemUnderTest();
State state = new State() {
@Override public void update() {
this.varOne = 42;
}
}
// rest of your test
}
At that point, you may not need Mockito at all. Be warned, though: This can get a little tricky if State has side-effects or is otherwise difficult to instantiate. You could extract an interface, which would require you to wrap your fields in getters; you could also make State a named abstract class and then pass mock(MyAbstractState.class, CALLS_REAL_METHODS)
, but that gets particularly hairy when you consider that no initializer actually runs on that fake instance, and consequently the fields are all zero or null. If it's not simple, don't waste your time forcing a square peg into a round hole.
A more-common pattern for mocks is to use a custom Answer
, in which you can execute arbitrary code at the expense of type safety. Here's an example, assuming update
is void and varThree
is an integer:
@Test public void yourTest() {
SystemUnderTest sut = createSystemUnderTest();
final State s = Mockito.mock(State.class);
doAnswer(new Answer<Void>() {
@Override public Void answer(InvocationOnMock invocation) {
int actualVarThree = (int) invocation.getArguments()[0];
assertEquals(EXPECTED_VAR_THREE, actualVarThree);
s.varOne = actualVarThree;
return null;
}
}).when(s).update(anyInt());
// rest of your test
}
Note that the array of arguments is an Object[]
, so the cast is necessary and slightly-dangerous, but then you can make all sorts of assertions and modifications synchronously when your mocked method is called.
Hope that helps!
Upvotes: 5