Malcolm O'Hare
Malcolm O'Hare

Reputation: 4999

Unit testing method parameter verification best practice

What is the best practice for verifying a method call with complex parameters when unit testing?

Say I'm testing a function like this:

class ClassA {
  ClassB dependency;

  void someFunction(SomeInputForA input) {
    // do some thing
    dependency.anotherFunction(differentInput);
  }
}

The two options that I can think of for verifying that someFunction calls anotherFunction with the proper input are:

A) do a verify on the mock of dependency for calling anotherFunction

unitUnderTest.dependency = mockClassB;

InputClass expectedDifferentInput = ... ;

verify(mockClassB).anotherFunction(expectedDifferentInput);

B) do an argument captor on the call of anotherFunction and assert the properties

unitUnderTest.dependency = mockClassB;

ArgumentCaptor<InputClass> captor = ArgumentCaptor.for(InputClass.class);

verify(mockClassB).anotherFunction(captor.capture());

InputClass capturedInput = captor.getValue();
assertEquals(value, capturedInput.param1);
// and more asserts afterwards

Is there a suggested path here? I'd lean towards the captor method because it feels more rigorous and is not relying on objects equals implementations being proper.

Thoughts?

Upvotes: 1

Views: 997

Answers (3)

Evan Reynolds
Evan Reynolds

Reputation: 450

I've used argument captors but VERY sparingly. The biggest issue you run into is that this route creates fragile unit tests. And no one is happy when they make a small change to a class and then find themselves struggling with unit tests in calling classes that shouldn't have been affected.

That being said, absolutely you have to eventually ensure that correct calls are made. But if you rely on an equals override working, then you are relying on that class having an equals method that works, and this is then part of that class's contract (and unit tested in that class) which is reasonable.

So that's why I'd vote for keeping it simple and just using verify. Same thing in the end, but your unit test is just less fragile.

Upvotes: 0

Taugenichts
Taugenichts

Reputation: 1365

You can always use matchers on the object passed into mockClassB.anotherFunction(). For example, if you want to compare fields on an object, you can write:

Matcher<YourClass> yourMatcher = Matchers.hasProperty("yourProperty", Matchers.equals(whatever));
verify(mockClassB).anotherFunction(argThat(yourMatcher));

I prefer this way, since you can share syntax for the when and the verify for the matchers and you can combine any combination of matchers. You just need to include the latest mockito and hamcrest libraries to get this to work.

Upvotes: 0

UserF40
UserF40

Reputation: 3611

Is differentInput computated off input?

If so then your B) is the better way to go as you are saying for Input A, you expect ClassA to change this to expectedDifferentInput and want to verify the delegating class (ClassB) is called. You are verifying the transformation of the input and delegating logic of ClassA.

If differentInput has no relation to input then you don't need to use the captor as really you are just checking delegation.

Any public caller to someFunction on ClassA shouldn't need to know about ClassB so it can be said both methods A) and B) are actually white box testing, in this case and so you might as well use the captors anyway. As you vary your input to someFunction, captors may also help you to identify edge cases if differentInput is computed off input.

Upvotes: 1

Related Questions