In this recipe, we will stub a method that returns a value so that it returns a custom answer of our choice.
This recipe is the last that will reuse the example from the previous recipe, which is related to a class that calculates an average value of tax factors. The starting point is the AverageTaxFactorCalculator
class and its collaborator is TaxFactorFetcher
, which is the provider of those values. The latter class picks one of the tax factors from the database (we'll stub that method). We will test those two classes as a unit. For your convenience, even though it violates the don't repeat yourself (DRY) principle, we will see the classes as follows so that you don't have to scroll around the book too much:
public class AverageTaxFactorCalculator { private final TaxFactorFetcher taxFactorFetcher; public AverageTaxFactorCalculator(TaxFactorFetcher taxFactorFetcher) { this.taxFactorFetcher = taxFactorFetcher; } public double calculateAvgTaxFactorFor(Person person) { double taxFactor = taxFactorFetcher.getTaxFactorFromDb(person); double anotherTaxFactor = taxFactorFetcher.getTaxFactorFor(person); return (taxFactor + anotherTaxFactor) / 2; } }
You can find the TaxFactorFetcher
collaborator class in the following code:
public class TaxFactorFetcher { static final double NO_COUNTRY_TAX_FACTOR = 0.3; static final double DEFAULT_TAX_FACTOR = 0.5; static final double DB_TAX_FACTOR = 0.8; public double getTaxFactorFor(Person person) { if (person.isCountryDefined()) { return DEFAULT_TAX_FACTOR; } return NO_COUNTRY_TAX_FACTOR; } public double getTaxFactorFromDb(Person person) { // simulation of DB access return DB_TAX_FACTOR; } }
We'll stub the method that accesses the database in such a way that we will register a callback (an answer) that will check if the person has provided information about his country of origin. Based on that piece of data, we will return a specific value. To do this, you have to perform the following steps:
BDDMockito.willAnswer(answer).given(spy).methodToStub()
. Or, in the standard manner, call Mockito.doAnswer(answer).when(spy).methodToStub()
.willAnswer(...)
or doAnswer(...)
, and pass the spy itself in the given(...)
or when(...)
method.willAnswer(answer1, answer2).given(taxFetcher).getTax();
As shown in the preceding line of code, regardless of the number of taxFetcher.getTax()
method executions, you will first throw exception1
and then always throw exception2
(until stubbed again).
Let's check the JUnit test. See Chapter 1, Getting Started with Mockito, for the TestNG configuration (I'm using the BDDMockito.given(...)
and AssertJ's BDDAssertions.then(...)
static methods; check out Chapter 7, Verifying Behavior with Object Matchers, on how to work with AssertJ or how to do the same with Hamcrest's assertThat(...)
). Have a look at the following code:
@RunWith(MockitoJUnitRunner.class) public class AverageTaxFactorCalculatorTest { @Spy TaxFactorFetcher taxFactorFetcher; @InjectMocks AverageTaxFactorCalculator systemUnderTest; @Test public void should_return_incremented_tax_factor_while_trying_to_calculate_mean_tax_factor_for_a_person_from_undefined_country() { // given final double expectedTaxFactor = 107; willAnswer(withTaxFactorDependingOnPersonOrigin()).given(taxFactorFetcher).getTaxFactorFromDb(any(Person.class)); // when double avgTaxFactor = systemUnderTest.calculateAvgTaxFactorFor(new Person()); // then then(avgTaxFactor).isEqualTo(expectedTaxFactor); } private Answer<Object> withTaxFactorDependingOnPersonOrigin() { return new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { double baseTaxFactor = 50; double incrementedTaxFactor = 200; if (invocation.getArguments().length > 0) { Person person = (Person) invocation.getArguments()[0]; if (!person.isCountryDefined()) { return incrementedTaxFactor; } } return baseTaxFactor; } }; } }
A spy is a special case of a mock. Refer to the Stubbing methods so that they return custom answers recipe of Chapter 4, Stubbing Behavior of Mocks, for more information.
Mockito allows you to provide a series of possible answers to the stubbed method, either by using the fluent interface API or by means of varargs.
If you need to execute a series of answers from the stubbed spy's method using the fluent API, you will have to stub the method invocation as follows:
willAnswer(answer1).willAnswer(answer2).given(spy).methodToStub()
Or, if you want to use varargs, you'd have do it as follows:
willAnswer(answer1, answer2).given(spy).methodToStub()
doReturn()
|doThrow()
|doAnswer()
|doNothing()
|doCallRealMethod()
family of methods at docs.mockito.googlecode.com/hg/1.9.5/org/mockito/Mockito.html#123.15.140.68