• 10 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 1/4/21

Use Mockito to mock collaborators when doing TDD

A punching bag, a crash test dummy, and a race car simulator. What do these three have in common? Other than taking a pounding, they are stand-ins for something real. For example, a punching bag allows you to practice boxing without having to punch someone. đŸ„Š A crash test dummy allows engineers to measure the safety of their cars without hurting actual people. 🚙 A racing car simulator helps you safely practice being a world-famous driver, but with infinite retries! 🏎

In software testing, you typically use a stand-in actor to play the part of a class you need to use; this is called a test double. Essentially, you use some code that pretends to be the thing you need for your tests.

We showed you some mocks in the first chapter. They are one example of a test double, which plays an important role in unit tests. Imagine a magic putty you could turn into either a punching bag or a crash-test dummy. Mocks have that kind of versatility when it comes to pretending to be other classes and interfaces. Pick the thing you want to mock, define it, and say “presto!” They also allow you to test how you used the mock, so you can be sure its acting was suitably convincing.

Develop a process for mocking

Consider the following approach to help you stay mindful and always on the lookout for mocking opportunities:

  1. Identify a single behavior you’re testing with your class under test (CUT).

  2. Question which classes are needed to fulfill that behavior.

  3. Consider all classes, other than your CUT candidates, for mocking.

  4. Eliminate any which are used for little other than to carry values around.

  5. Set up the required mocks.

  6. Test your CUT.

  7. Verify that your mocks were used correctly.

Let’s consider where you might use a mock. Look at the following code which contains a lot of unnecessary testing:

public class CalculatorServiceTest {
    
    Calculator calculator = new Calculator();
    // Calcualtor IS CALLED BY CalculatorService
    CalcualtorService classUnderTest = new CalculatorService(
        calculator );

    @Test
    public void add_returnsTheSum_ofTwoPositiveNumbers() {
        int result = classUnderTest.calculate(
            new CalculationModel(ADDITION, 1, 2).getSolutions();
        assertThat(result, is(3));
    }
    
    @Test
    public void add_returnsTheSum_ofTwoNegativeNumbers() {
        int result = classUnderTest.calculate(
            new CalculationModel(ADDITION, -1, 2).getSolution());

        assertThat(result, is(1));
    }

    @Test
    public void add_returnsTheSum_ofZeroAndZero() {
        int result = classUnderTest.calculate(
            new CalculationModel(ADDITION, 0, 0).getSolution());
        assertThat(result, is(0));
    }
    ...
}

Can you see how the CalculatorService test for an addition calls back to a real Calculator instance? This test definitely gives us confidence that Calculator and CalculatorService work well together, but it looks to me like we’re testing two classes here! In fact, since would have already tested Calculator in CalculatorTest, we are essentially testing it twice! 

What’s wrong with that? 

When unit testing, you need to focus on making sure that your class under test fulfills its own promises. Remember the F.I.R.S.T. and keeping tests isolated principles? One unit test, one unit tested.

So what do I do now? CalulationService has one responsibility to get right: solving calculations defined by CalculationModels . That’s it. You can test for happy and sad path situations without retesting the Calculator. The Calculator, on the other hand, is not the class you’re testing, but something to help your CUT test do its job. This is called a collaborator, and it’s a prime candidate for a mock.

To replace Calculator with some mocks, you’ll need to use Mockito, the most popular mocking library on the JVM. Let’s get it up and running! We'll start with a bad example of a test which doesn't use mocks, and rewrite it to mock its collaborators:

In the screencast, we started with a bad test, which seemed to repeat scenarios already tested within our calculator. As you saw, this was easily resolved by rewriting our tests to use a mocked Calculator to test for a happy path response.

Did you notice how easily we used Mockito by annotating the class with  @RunWith(MockitoJUnitRunner.class)  and used the  @Mock  annotation above our declaration of the Calculator field? That’s all it took to turn it into a fake. 

Our goal was to test if CalculationService can create a CalculatorModel which contains the answer given back to it by the calculator. As demonstrated, this doesn’t mean that we need to re-test calculating the response! In fact, we didn’t even need a real calculator at all. The Calculator class had its own unit tests.

By using our mocks, we were able to focus on testing that CalcualtorService could turn a CalculatorModel into a solution!

Mocking happy paths

Have a look at the CalculatorService test, which collaborates with an instance of the Calculator class.

public class CalculatorServiceTest {
    @Mock
    Calculator calculator;
    
    CalculatorService underTest = new CalculatorService(calculator);
    
    @Test
    public void calculate_shouldUseCalculator_forAddition() {
        // ARRANGE
        when(calculator.add(-1, 2)).thenReturn(1);
        // ACT
        int result = classUnderTest.calculate(
            new CalculationModel(ADDITION, -1, 2).getSolution());
        // ASSERT    
        verify(calculator).add(-1, 2);
    }
}

At line 10, Mockito’s  when() method is used to set up our Calculator mock, so that it knows what it should do when its add(int, int) method is given certain arguments. In our example, we use when() to respond to calculator.add(-1, 2).  We tell it what to return with  thenReturn(1). 

Simply put, by declaring  when(calculator.add(-1, 2)).thenReturn(1), our test can use a CalculatorService without needing a real Calculator instance! Well, at least for a test which attempts to prove that we use a calculator to solve "-1 + 2."

This is just an informed assumption about how calculator should behave. We can trust this assumption, as it’s ideally verified by a test case in the CalculatorTest unit tests. If there wasn’t a Calculator test which covered this educated guess, it would make sense to add it!

At line 15 above, we use the  verify() method to make sure that we really used our calculator mock! Don't worry. I'll explain this!

Testing how you use your mocks

When you use mocks, you want to be sure that you use them realistically. That is that you're capturing the real behavior of what you’re mocking.

What if you had a method which took a List<Calculation>, and returned a List<CalculationModel>? These are just classes created using the responses from the collaborator. How do you check that your collaborator was actually called once for each calculation?

If you want to ensure that you're using the calculator correctly, you can use  verify(calculator).divide(4, 2) to make sure you called it once. It's better to be specific, but if you didn’t know what those arguments were going to be, or if you didn’t care, you could replace them with Mockito’s any()

verify(calculator).divide(any(Integer.class), any(Integer.class))

Being more specific makes sure that you don’t end up getting false confidence.

 As you saw, you can use times() to ensure that you only called the divide method once.

If you were testing a method which called the add method multiple times, you could test this by passing the value of 3 to the times() method:

verify(calculator, times(3)).add(any(Integer.class), any(Integer.class))

That any() can also be replaced with some other real value.

Mocking exceptions

You can also use Mockito to test how you respond to exceptions thrown by your collaborator. Imagine that calculator was asked to divide by zero. This would result in an ArithmeticException being thrown by our calculator. Mockito is so cool that it even lets you fake this! You can also throw a variety of action types to test the resiliency of your software before it fails in the real world with real users.

This process is identical to a happy path; however, instead of  .thenReturn, use  .thenThrow(new IllegalArgumentException("An Exception Reason")).

Simply put, by using the following declaration, our CalculatorService will receive an exception from our calculator when attempting to use it to divide two by zero:

when(calculator.divide(2, 0)).thenThrow(new IllegalArgumentException("Cannot divide by zero"));

Let’s see how we can test for this:

Can you see how this allows you to protect users from that horrible exception? The CalculatorService can now be tested to make sure it takes that exception and generates an appropriate error message in the CalculationResult.

Verify calling a collaborator

What if I wanted to test that by passing a null argument to the CalculatorService's  calculate()  method, it fails early and never calls the calculator?

If you expect your code to handle any bad arguments, it makes sense to avoid inadvertently sending those bad parameters to your collaborator. Well, Mockito has you covered again. You can use never() in much the same way as times() above. This is great for catching issues where your control flow doesn't act the way you intended it to. These can come back as bugs down the line!

verify(calculator, never()).add(any(Integer.class), any(Integer.class));

Let's see this in action:

Did you see how I refactored from  never() to use verifyZeroInteractions()? This method ensures that you don't have any collaborations with your calculator, and avoids the need to specify which arguments were used. It's sufficient to just declare  verifyZeroInteractions(calculatorMock)  to ensure you never used that mock in any way.

Using Mockito to discover the collaborators you need

Imagine your technical lead asked you to update that CalclationService so it could present user-friendly versions of large numbers. She gives you the example of showing the number 100020 as “100,020.” Let’s see how you can use mocking, interfaces, and the principles discussed earlier to deliver this without building the wrong behavior into CalculationService.

Did you see how you needed SolutionFormatter and so created a Java interface, followed by a mock to finish testing and understand how you’d want to use it? The story doesn’t end there. You would then implement the concrete version of that class using TDD, and it too would get its own tests. You might discover another collaborator to mock and repeat the process until you no longer have anything left to mock and can fulfill that behavior you’re testing.

Try it out for yourself!

See if you can run the tests in the repository. The code for this chapter is on the p2-c4-sc1-to-5 branch of the following repository:

git clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJava.git
git checkout p2-c4-sc1-to-5 

If you haven't already, use your IDE to clone the repository and check out the branch p2-c4-sc1-to-5 . You can now explore the examples from the screencast and try running the tests with:  

mvn test

Remember to read the  README.md  on this branch for tips and details! Have fun! 😉

Let's recap!

  • In software testing, you typically use a stand-in actor to play the part of a class you need to use; this stand-in actor is called a test double.

  • A mock is a type of test double which also lets you test how its interacted with.

  • Mockito lets you declaratively define how your mock should behave.

  • When doing TDD, you can pinpoint where you need new collaborators. This means that when you come across behaviors which don’t naturally fit with your CUT, you can create new interfaces and mocks for them. 

Example of certificate of achievement
Example of certificate of achievement