Like any stage actor, your test doubles will be expected to take on a number of different roles, but they’ll need some advanced training to master the most difficult ones. In this chapter, we’re going to spend some more time with Mockito, and see how to make it perform when testing more complex scenarios. Let’s get started!
Using mocks to test how your collaborator was called
When we used Mockito’s when()
earlier, we knew what values our mock would eventually get used to call its methods, by our class under test (CUT). For instance, we could use the when()
command using explicit values of 1 and 2 to stub out our add command:
when(calculatorServiceMock.add(1, 2)).thenReturn(3);
If your CUT then called one of its collaborators which required the calculation to be defined in a CalculationModel, you might see a call similar to the following hidden deep in the code:
CalculationModel model = new CalculationModel(CalculationType.ADD, 1, 2);
calculationService.calculate(model);
If that code lives in your CUT, your test is not going to have access to the model instance that was created there. You would not even be able to define how your CalculatorService mock should behave using when()
. Specifically, this means that you have no way to do a when(calculatorServiceMock.calculate(model))
! Why? Because only the CUT has access to that model, since it created it.
If your test defines a mock’s behavior with when()
, you need to have access to the actual objects which the mock gets called with. When these are deeper data structures and are created inside the CUT, you don’t have access to them.
So how do we define what our mock should do in such complex situations?
If you don’t have access to the actual object references which your mock class is called with, like the above CalculatorModel, the next best thing to do is use Mockito’s any()
matcher.
You can use this in a when()
statement in place of those arguments you don’t have access to like the CalculationModel instance! The behavior of a call to CalculatorService’s calculatorService.calculate(model)
could instead be specified with when(calculatorService.calculate(any(CalculationModel.class)))
.
Your mock will just work whenever someone calls the method with any value: calculatorService.calculate(<with any CalculationModel>
. ;)
Hold on! If I’m triggering my mock on any argument, how can I make sure that the mock was used correctly?
You’re right, if you’ve mocked a relationship with any()
, the mock will respond to any value you pass it! Your tests might still pass, but your mock wouldn’t really represent the behavior of the real class! 😖 Sure, this works around your not having been able to use when()
against the actual instance of CalculationModel
.
This won’t give you any real confidence as your code may be working by coincidence. Fortunately, Mockito also has the ArgumentCaptor class, which allows you to check how your mock was called by the CUT after this has happened.
ArgumentCaptor is essentially an interceptor, which can be used to determine how your mocks were used by your CUT. As the name suggests, ArgumentCaptor catches and abducts the arguments you pass to a mocked method! Remember (when specified) how our mock should be called before the ACT part of our test? Well, with ArgumentCaptor, you can allow the code to use the mock in whatever way it likes. Once you get to ASSERT, you can interrogate the captive, and check how it was used with normal assertions.
Why would you want to do all this? Let’s have a look at some code to see what this looks like:
You can see that we used any()
at line 104 when defining our mock. ArgumentCaptor is then used at line 111 by calling calculationModelCaptor.capture()
within a verify. This captures the arguments used to call the mock.
To test how we used the mock in the ASSERT section, we then called calculationModelCaptor.getAllValues()
to pull out the actual values which our test class used to call the mocked CalculatorService.calculate()
. This allowed us to grab the actual instance of CalculationModel, which was used in the call to CalculationService, and test it explicitly with normal asserts.
ArgumentCaptor helps ensure that you use your mock according to its contract. When your CUT is responsible for creating complex nested classes and passing them to a collaborator, it’s easy to let a mistake slip. any()
essentially says “anything goes!” With ArgumentCaptor, you get to hold up a stop signal and check that you’re happy with those complex data structures which were passed to your mocks. This can save you from setting up mocks which give you false confidence.
Let’s see all of this in action and test that our BatchCalculator creates the right CalculatorModel when it uses our CalculatorService:
Since you have to pass your argument captor to the verify method, you are forced to do more than one assert. While it’s a good principle to have one assert per test, you'll sometimes need more. If you find yourself in this situation, tend towards fewer.
Remember that the AAA principle is about the clarity of what is being tested and aim for the best you can do. In the worst case scenario, you won’t be able to quickly understand what’s gone wrong when something fails.
Mocking how a collaborator answers when called multiple times
So far, you’ve only seen collaborators which you mock once, test, and then throw away. Calling thenReturn()
followed by a when()
will only return a single answer. What if your code has to call a collaborator several times and gets a different answer back on each call?
For instance, do you remember the BatchCalculator we looked at earlier? This was a class which read lots of different calculations from a file and then called the CalculatorService to solve multiple calculations.
How would we test for multiple answers in a single test case?
Let's consider how to test the CalculatorService mock for a file with many different calculations. You could write a bunch of tests for files with only one type of calculation in each. That would work, but you want to make sure you could handle more than one. This matters when working with batches! The answer is Mockito’s Answer interface. It provides a method into which you can program the logic to provide a different answer each time your mock is called.
Instead of thenReturn(...)
, you can call either use: doAnswer(..).when(myMock.myMethod(any(MyType.class)))
or
when(myMock(any(MyType.class))).thenAnswer(..)
.
The argument you pass doAnswer(..)
and thenAnswer(..)
will be an instance of a class which implements Mockito’s Answer Interface, and provides a specialized implementation of T answer(InvocationOnMock invocation)
method. You don’t really need to care about what InvocationOnMock does. All you need to do is return a different answer from this method each time it’s called.
We’re going to jump in and do that now:
Spying on real code which you can’t mock
Mocks can do a lot, but there are situations that Mockito won’t support. For instance, Mockito respects the existing type modifiers of Java classes, and in so doing, will not allow you to mock a final method on a class. This can be frustrating, but it’s good to accept that the author of your collaborator once declared the method as not being open to subclassing or any specialization. All we’re doing is respecting this.
So, how do I test these collaborations if I can’t mock my collaborator?
Spies are real classes which you can validate just like a mock. For instance, if you were adding the ability to calculate a statistical average to your CUT, you might use Java’s built-in IntSummaryStatistics class. This lets you sample integer values with its accept method, and then compute their mean with the getAverage()
method. Sadly, getAverage()
is marked final, and so is unmockable. Mockito would blow up with a horrible error if you tried! 💥
However, you can use Mockito’s spy to perform some programmatic espionage on it. Just like Mockito’s @Mock
annotation and mock(YourRealClass.class)
method, you get a very similar @Spy
and spy(YourRealClass.class)
method.
A spy is a partial mock, as it’s a real class and you can change its behavior by using Mockito. To understand this better, let’s compare spies and mocks:
Situation | Mock | Spy |
Create with the annotation | @Mock | @Spy |
Create with a method | mock(RealClass.class) | spy(RealClass.class) |
Calling someMethod after when(someMethod) .thenReturn(response) | Returns the provided response | Returns the provided response |
Calling someMethod() you’ve not defined with ‘when’ | The mock returns a null | The original method is called in the RealClass for a result |
Capturing arguments with ArgumentCaptor | verify(mock) .someMethod(captor) | verify(spy) .someMethod(captor) |
Mocking a final method or class | Mockito goes 💥 | Mockito goes 💥 |
As you can see, spies are essentially real objects which can be checked with verify. If you want, you can even change their behavior as you would a mock. Ultimately, they are a lot less fake.
Hold on, you mean a spy works with the real collaborator? Why would I ever use a mock again?!
It’s tempting to write unit tests which use real classes. If you don’t need to declare what the mock does, you’ll generally feel more comfortable about getting it right. Unfortunately, with each real instance of collaborator you integrate, you are compromising your F.I.R.S.T. principles.
Remember isolation and repeatability? Being explicit in defining what your tests should do, while removing a dependence on anything outside your CUT, allows you to write fast and targeted unit tests which prove your unit works as it should. There will still be other opportunities to prove that the pieces work together; remember the rest of the testing pyramid!
If you can’t mock, your next best bet is spying. Always try to maintain your isolation and use a mock first!
I’d like to give users the ability to calculate averages and use that IntSummaryStatistics
class as a calculator. Are you ready to see how to use a spy to validate our collaboration with it?
As you saw, we used the spy class to turn our instance of IntSummaryStatistics into a partial mock. The test we implemented together made sure that the accept method was called as expected. Tests in our class don’t stop there. We should test returned values from the getAverage() method and explore other scenarios.
Doing static mocking
When working on the BatchCalculatorFileReader, we needed to write a unit test which read a file from the file system containing sums which were calculated together. To ensure that our test was repeatable and isolated, I had to avoid using a file from the file system. The collaborators we used for this were the static Java methods Files.lines()
, which returned a stream of all the lines in a file and Paths.get()
, which returns the full path to a resource.
As you might remember, static methods and variables are shared and made available on the class itself. You don’t even need to call new()
. You can just call methods directly on the files and paths classes in this case. You can’t use Mockito here as you don’t have an instance of a class to mock. The logic to call Files.lines()
and Paths.get()
lives inside the CUT.
We're out of options! 😭 So, what can we do?!
Basically, the process here is to:
Try to rewrite your code to avoid using static methods.
If you can't do this, use a tool to mock static methods as a last resort.
Learn how to mock statics
It is often true that when something is hard to test, you need to stop and rethink the design of your software.
Sometimes you don’t have the option to change your code. Since we don’t live in an ideal world, there are times when you just have to work with the code given to you and don’t have time to refactor. In this situation, it’s okay to turn to PowerMock. This is the slow-draining kitchen sink of mocking. With great PowerMock comes great responsibility. It’s easy to create tight dependencies and tests which poke into all the wrong place. Having that power is also fun.
Sadly, PowerMock works by changing your Java code after it has been compiled. As you saw, when I ignored a slow test to respect the F.I.R.S.T. principles, PowerMock added as much as 2 seconds per test! Guess what? That slow test was the very same one we’re talking about here! Don't let that stop you. As slow as the test is, it does reduce some of your risks.
To add PowerMock to your project, add a Maven dependency to your POM, just as you did for Mockito. First, annotate your test with @RunWith(PowerMockRunner.class)
, and then, specify which class you’re going to static mock using @PrepareForTest({YourClassUnderTest.class})
. After that, mark any classes you want to mock static methods on using PowerMock’s mockStatic()
method. From there, it’s just like Mockito. You can use when and verify exactly as you would have with Mockito:
Wait! Can I just use statics methods and never call new again?
Nooo! 😱 While all these methods don’t need you to call new, sadly, this also means that you don’t have a way to control or verify how collaborations occur. For instance, you might not be able to test negative scenarios easily. In this case, it also makes it hard to change the implementation which your class uses for reading files. The call to Files.lines()
is glued between the lines of your CUT, and so it becomes hard to test it for a range of behaviors.
You might think this is fine given the reduction in complexity. Let’s consider an example with more obvious consequences. Think about what would happen if you were calling Instance.now()
the Java API for returning the current time. For every test that called this, you’d end up with a different instant depending on the time at which it was run. There goes the repeatability of your tests! Fortunately, there’s a workaround for this!
The secret is in Clock.fixed( Instant.parse("2019-01-01T00:00:00Z"),
ZoneOffset.UTC))
.
You can’t always know every handy workaround for testing every static you use, and most won’t have an easy approach to facilitate their testing.
The JDK can fake time using Clock.fixed( Instant.parse("2019-01-01T00:00:00Z"), ZoneOffset.UTC))
.
This is the line which makes time travel possible! The value 2019-01-31T00:01:20Z is just a date and time. You can see the date to the left of the T, which is the 31st of January 2019. On the right of the T, you have the time, which is 00:01 AM and 20 seconds. The ZoneOffset ensures that we’re talking about the UTC timezone. Most software systems are built to store their data in UTC so that conversions can be made as needed. It stands for Universal Time Coordinated and is the same as Greenwich Mean Time.
If a call to a static is buried deep in one of your methods, how will you test that a method like Files.lines()
was called correctly for all situations by your code? Do you remember watching me write a test which worked on windows but broke when run in another operating system?
Can I just test the final result instead of all these collaborations?
Yes, you can! But...isn’t that an integration test? 🤔
You can test a broad number of different scenarios to ensure that your CUT correctly uses a static method and that they collaborate towards returning an appropriate result. At this stage, it's on you to retest that you correctly integrate with the static method and you might end up retesting scenarios it’s proven against.
Other ways to avoid PowerMock
A common pattern is to build a small class around the class with the static method, so that you have a mockable API. Since it does nothing other than pass values to and from the static method, you have little risk of introducing an issue this way. Here’s an example:
public class FilesWrapper {
public Stream<String> lines(Path path) throws IOException {
return Files.lines(path);
}
}
The wrapper here does nothing other than pass values onto Files.lines and returns the result. As with any other test, making sure you collaborate with it correctly and call its methods with sensible values reduces your guesswork and the risk of not covering all your bases.
As with many of the scenarios you’ve seen, one of the benefits of using mocks is that you can validate how you collaborated with other code you need to fulfill your job. This is that extra bit of risk mitigation which can help you.
Try it out for yourself!
See if you can run the tests in the repository. The code for this chapter is on the p2-c5-sc1-to-4 branch of the following repository:
git clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJava.git git checkout p2-c5-sc1-to-4
Or use your IDE to clone the repository and check out the branch p2-c5-sc1-to-4. You can now explore the examples from the screencast and try running the tests with:
mvn test
If you feel like having a go, see if you can update the BatchCalculatorTest to process a batch file with division, addition, and multiplication. You can use Mockito's Answers to make that test pass.
Have fun! 🙂
Let's recap!
Use ArgumentCaptor to capture the actual arguments which your mock is called with. This allows you to verify the correctness of a collaboration. Especially when your test class uses arguments which you have no visibility of in your tests.
Use Mockito’s doAnswer static method to set up a mock which needs to be called multiple times.
@Spy
andspy()
allow you to use real instances of a class, but call when and verify against it, treating it as a partial mock. Just remember that real classes break the isolation of your test.If you have to mock a static method use PowerMockito’s mockStatic.