Fast Facts
Get a quick overview of this blog
Isolate unit tests with Mockito mocks to focus on your code's behavior.
Define mock behavior with when() and thenReturn() for granular control.
Mock beyond basics: explore spies and static mocks for complex tests.
Auto generate mocks with HyperTest that lets you create and update mocks for all the other external dependencies.
💡 Mockito is unreadable for a beginner. So I'm just starting with mockito on Java, and god, it's horrible to read. I mean, reading tests in general requires some practice, but when you get there is like documentation on class methods. Is wonderful. Mockito test, on the other hand, are chaotic.
-a mockito user on Reddit
Well, that’s not a good review for such a famous mocking framework. People have their reasons to have varied opinions, but this guide is our attempt to make mockito sorted for you all.
So what is mockito all about?
Unit testing – the cornerstone of building reliable, maintainable software. But unit testing can get tricky when you have complex dependencies. That's where Mockito mocks come in, like a superhero for isolated unit tests.
Mockito is one of the most popular and powerful mocking frameworks used in Java unit testing. It simplifies the creation of test doubles, or "mocks", which mimic the behavior of complex, real objects in a controlled way, allowing developers to focus on the behavior being tested without setting up elaborate real object environments.
Mockito allows testing a method without needing the methods that the method depends on.
Introduction to Mocking
Mocking is a technique used in unit testing where real implementation details are replaced with simulated behaviors. Mock objects return predetermined responses to method calls, ensuring that the test environment is both controlled and predictable. This is crucial in testing the interactions between components without relying on external dependencies.
⏩Mocks
Imagine a mock object as a spy. It pretends to be a real object your code interacts with, but you control its behavior entirely. This lets you test your code's logic in isolation, without worrying about external factors.
Why Mockito?
Mockito’s ease of use and large community-base is great, but there are other reasons also on why it’s a favored choice among Java devs:
Flexibility: It allows testing in isolation and provides numerous ways to tailor mock behavior.
Readability: Mockito's syntax is considered clear and concise, making your tests easier to understand and maintain.
Versatility: It supports mocking both interfaces and classes, offering flexibility in your testing approach.
On the technical front, it offers customizations up to the level of fine-tuning the details in your verifications, keeping tests focused on what matters. Also:
Spies: Mockito allows creating spies, which are a type of mock that also record how they were interacted with during the test.
Annotations: Mockito provides annotations like @Mock and @InjectMocks for streamlined mock creation and injection, reducing boilerplate code.
PowerMock: Mockito integrates with PowerMock, an extension that enables mocking static methods and final classes, giving you more control in complex scenarios.
While other frameworks like EasyMock or JMockit may have their strengths, Mockito's overall ease of use, clear syntax, and extensive features make it a preferred choice for many Java developers.
Getting Started with Mockito
Before right away starting the tech-dive with mockito, let’s first understand some basic jargon terms that comes along with Mockito.
Understanding the Jargon first:
Mocking: In Mockito, mocking refers to creating a simulated object that imitates the behavior of a real object you depend on in your code. This allows you to isolate and test your code's functionality without relying on external factors or complex dependencies.
Mock Object: A mock object is the fake implementation you create using Mockito. It can be a mock for an interface or a class. You define how the mock object responds when methods are called on it during your tests.
Stub: While similar to a mock object, a stub is a simpler version. It often provides pre-programmed responses to specific method calls and doesn't offer the same level of flexibility as a full-fledged mock object.
Verification: Mockito allows you to verify interactions with your mock objects. This means checking if a specific method on a mock object was called with certain arguments a particular number of times during your test. Verification helps ensure your code interacts with the mock object as expected.
@Mock: This annotation instructs Mockito to create a mock object for the specified class or interface.
@InjectMocks: This annotation simplifies dependency injection. It tells Mockito to inject the mock objects created with @Mock into the fields annotated with @InjectMocks in your test class
Mockito.when(): This method is used to define the behavior of your mock objects. You specify the method call on the mock object and the value it should return or the action it should perform when that method is invoked.
Mockito.verify(): This method is used for verification. You specify the method call you want to verify on a mock object and optionally, the number of times it should have been called.
Now it’s time to see Mockito in practice
Alright, picture a FinTech app. It has two important services:
AccountService: This service retrieves information about your account, like the account number.
TransactionService: This service handles transactions, like processing a payment.
We'll be using Mockito to mock these services so we can test our main application logic without relying on actual accounts or transactions (safer for our virtual wallet!).
Step 1: Gearing Up (Adding Mockito)
First, we need to include the Mockito library in our project. This is like getting the deck of cards (Mockito) for our testing house of cards. You'll use a tool like Maven or Gradle to manage dependencies, but don't worry about the specifics for now.
Step 2: Mocking the Services (Creating Fake Cards)
Now, let's create mock objects for our AccountService and TransactionService. We'll use special annotations provided by Mockito to do this:
@Mock
private AccountService accountService;
@Mock
private TransactionService transactionService;
// More code will come here...
@Mock: This annotation tells Mockito to create fake versions of AccountService and TransactionService for us to play with in our tests.
Step 3: Putting it all Together (Building the Test)
We'll create a test class to see how our FinTech app behaves. Here's a breakdown of what goes inside:
@RunWith(MockitoJUnitRunner.class)
public class MyFinTechAppTest {
@InjectMocks
private MyFinTechApp finTechApp;
@Before
public void setUp() {
// This line is important!
MockitoAnnotations.initMocks(this);
}
// Our test cases will go here...
}
@RunWith(MockitoJUnitRunner.class): This line tells JUnit (the testing framework) to use Mockito's test runner. Think of it as the table where we'll build our house of cards.
@InjectMocks: This injects our mock objects (accountService and transactionService) into our finTechApp instance. It's like shuffling the deck (our mocks) and placing them conveniently next to our app (finTechApp) for the test.
@Before: This ensures that Mockito properly initializes our mocks before each test case runs. It's like making sure we have a clean deck before each round of playing cards.
Step 4: Test Case 1 - Valid Transaction (Building a Successful House of Cards)
Let's create a test scenario where a transaction is successful. Here's how we'd set it up:
@Test
public void testProcessTransaction_Valid() {
// What should the mock AccountService return?
Mockito.when(accountService.getAccountNumber()).thenReturn("1234567890");
// What should the mock TransactionService do?
Mockito.when(transactionService.processTransaction(1000.00, "1234567890")).thenReturn(true);
// Call the method in our app that processes the transaction
boolean result = finTechApp.processTransaction(1000.0)
Advanced Features
Spy
While mocks return predefined outputs, spies wrap real objects, optionally overriding some methods while keeping the original behavior of others:
List list = new ArrayList();
List spyList = Mockito.spy(list);
// Use spy object as you would with a mock.
when(spyList.size()).thenReturn(100);
Capturing Arguments
For verifying parameters passed to mock methods, Mockito provides ArgumentCaptor:
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
verify(mockedList).get(captor.capture());
assertEquals(Integer.valueOf(0), captor.getValue());
A better approach to Mockito Mocks
Mocks generated by mockito are useful, considering the isolation it provides. But the same work can be eased out and performed better by HyperTest.
HyperTest mocks external components and auto-refreshes mocks when dependencies change behavior. It smartly mocks external systems like databases, queues, downstream or 3rd party APIs that your code interacts with.
It also smartly auto-refreshes these mocks as dependencies change their behavior keeping tests non-flaky, deterministic, trustworthy and consistent.
Know more about this approach here in our exclusive whitepaper.
Conclusion
Mockito mocks offer a robust framework for effectively isolating unit tests from external dependencies and ensuring that components interact correctly. By understanding and utilizing the various features of Mockito, developers can write cleaner, more maintainable, and reliable tests, enhancing the overall quality of software projects.
To know more about the automated mock generation process of HyperTest, read it here.
Related to Integration Testing