5

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.

Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402
  • 2
    The suspicious thing about your test is that your mock becomes the system under test in your 3rd example. You can make the mock return whatever you want, but you want to test **the mock** returns something under a certain condition. Can't you restructure your code so that your SUT is one thing instead of 1.x things? – Daniel Kaplan Jun 25 '13 at 21:05
  • It sounds to me like you've got multiple assertions going on within the same test case. Can you break your test case down into smaller test cases that test just one condition each? You should never need to have any conditional logic in a mock. If the code that you're testing calls the mock twice, and requires different returns each time, then simply pass multiple values to your `thenReturn` call (as in my example at http://stackoverflow.com/questions/8088179/using-mockito-with-multiple-calls-to-the-same-method-with-the-same-arguments/8395685#8395685). – Dawood ibn Kareem Jun 26 '13 at 02:42
  • @DavidWallace: As noted in the question, I don't want to create a chain of return values, because that makes the test dependent on the implementation rather than just on the contract (i.e. "must get `varOne` twice, then call `update()`, then get it again, else the test fails even though the contract - maybe - is still fullfilled). – Tomas Aschan Jun 26 '13 at 09:59
  • @tieTYT: The `State` interface is not under my control, and will not be instantiated by me in the application, so there's no (straightforward) way I can change the way it works. Since I also test the return value of the sut, the actual verification isn't that the mock returns something under a certain condition, but rather that *if* the mock does that, *then* the sut fulfills its contract. – Tomas Aschan Jun 26 '13 at 10:02

1 Answers1

4

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!

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • The answer technique is what I ended up with. `State` is a type which is not under my control, with a bunch of side effects etc, so the "best" approaches are unfortunately not available to me. Thanks for the extensive answer! – Tomas Aschan Jun 26 '13 at 09:57