Adding Mockito hints to exception messages (JUnit) (Experimental)
When a JUnit test fails, an exception is thrown and a message is presented. Sometimes, it is enough to find a reason for this mistake and to find the solution. Mockito, however, goes a step further and tries to help the developer by giving him additional hints regarding the state of the stubbed methods.
Getting ready
For this recipe, let's assume that our system is the MeanTaxFactorCalculator
class that calculates tax through TaxService
, which has two methods—performAdditionalCalculation()
and getCurrentTaxFactorFor(...)
. For the sake of this example, let's assume that only the latter is used to calculate the mean value:
public class MeanTaxFactorCalculator { private final TaxService taxService; public MeanTaxFactorCalculator(TaxService taxService) { this.taxService = taxService; } public double calculateMeanTaxFactorFor(Person person) { double currentTaxFactor = taxService.getCurrentTaxFactorFor(person); double anotherTaxFactor = taxService.getCurrentTaxFactorFor(person); return (currentTaxFactor + anotherTaxFactor) / 2; } }
We wanted to check whether our system under test is calculating the proper result, so we wrote the following test but made a mistake and stubbed a wrong method (I'm using the BDDMockito.given(...)
and AssertJ's BDDAssertions.then(...)
static methods—refer Chapter 7, Verifying Behavior with Object Matchers, for how to work with AssertJ or how to do the same with Hamcrest's assertThat(...)
method):
@RunWith(MockitoJUnitRunner.class) public class MeanTaxFactorCalculatorTest { static final double UNUSED_VALUE = 10; @Test public void should_calculate_mean_tax_factor() { // given TaxService taxService = given(Mockito.mock(TaxService.class).performAdditionalCalculation()).willReturn(UNUSED_VALUE).getMock(); MeanTaxFactorCalculator systemUnderTest =new MeanTaxFactorCalculator(taxService); // when double meanTaxFactor = systemUnderTest.calculateMeanTaxFactorFor(new Person()); // then then(meanTaxFactor).isEqualTo(UNUSED_VALUE); } }
The test fails and what we can see is the standard JUnit comparison failure being thrown (presenting only the most important part of the stack trace) as follows:
org.junit.ComparisonFailure: Expected :10.0 Actual :0.0
Now let's take a look at how to use Mockito's experimental features to get more Mockito related information appended to the error message.
How to do it...
If you want to have more information presented in your error message, you have to perform the following steps:
- Annotate your JUnit test with
@RunWith(VerboseMockitoJUnitRunner.class)
. - Define your mocks and perform stubbing inside the test method (unfortunately, you can't use annotations or initialize fields outside test methods).
What happens next is that additional exception messages can be seen in the exception that is thrown as follows:
org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings:contains both: actual test failure *and* Mockito warnings. This stubbing was never used -> at ...MeanTaxFactorCalculatorTest.should_calculate_mean_tax_factor(MeanTaxFactorCalculatorTest.java:30) *** The actual failure is because of: *** Expected :10.0 Actual :0.0
How it works...
When the test is run, VerboseMockitoJUnitRunner
appends a listener. When the test is started, this listener finds all the stubs through WarningsCollector
, including the unused stubs for given mocks.
As the Mockito developers state it in the code, they are indeed using a very hacky way to append a message to the thrown exception after the test fails. The JUnitFailureHacker
class is instantiated and, by means of the Whitebox
class, the internal state of a private field of the JUnit's Failure
object is modified with additional Mockito messages.