Tomas Aschan
Tomas Aschan

Reputation: 60574

Call changes mock object in mockito

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):

  1. If s.varOne is set, return its value.
  2. Else, if s.varTwo is set, return that instead.
  3. Else, if 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.)
  4. Else, throw an error.

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

Answers (1)

Jeff Bowman
Jeff Bowman

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

Related Questions