Mockito is a Java framework that allows you to mock or clone real-world objects.
In a nutshell, mockito is a BDD, or behavior-driven development, unit testing approach that allows you to verify the functionality of the code by mocking the objects rather than utilizing real objects.
With the mockito framework, we can not only mock or clone the object, but also control the behavior of the function.
Such as passing dummy values as input or instructing that if a specific method has been called on the given mocked object, simply return some dummy values instead of calling that function.
Why Mockito?
Mockito is one of the most popular mocking frameworks for Java, and it can be easily in combination with other popular unit testing frameworks in Java, such as JUnit and TestNG.
Mockito, a behavior-driven programming framework, has both an annotation base and a code base for mocking.
Mockito provides a wide range of functionalities, including stubbing method calls, verifying method calls, and validating method call order.
Mockito is capable of mocking complicated dependencies like as databases and web services.
Mockito allows you to test error handling using stub method calls that return errors. This enables you to test how the code in test handles errors. This can assist you in writing more robust code.
Dependencies are required to work with the mockito framework
Thanks to build tools including Gradle, Maven, etc., enabling behavior-driven development using the Mockito framework is now simple.
Simply include the script below in the build.gradle
In order to have the mockito in your application work with Gradle.
testImplementation 'org.mockito:mockito-core:5.6.0'(code-box)
The code below can be used to configure the mockito if you are using the Maven
build tool:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
If you are not familiar with Gradle or Maven, you may use any build tool, and mocktio library scripts can be found in the Maven central repository.
As previously explained, the mockito is simple to set up with other Java unit testing frameworks like as JUnit or TestNG.
Because I'm going to use JUnit 5 with mockito, I'll need to add one additional dependency to my project, which is stated below.
testImplementation 'org.mockito:mockito-junit-jupiter:5.6.0'(code-box)
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
The Mockito JUnit Jupiter library script can be found in the Maven central repository.
Mockito annotations
There are several annotations used in the mockito, such as @Mock
, @Spy
, @Captor
, @InjectMocks
and @ExtendWith(MockitoExtension.class)
, which we will discuss subsequently.
@Mock annotation
The @Mock annotation is used to mock or clone the object, which means you may change its behavior, such as function input, or supply some dummy output when it calls particular methods using the BDDMockito's when()
and then()
method.
The @Mock annotation is included in the org.mockito.Mock package
.
@Mock private EmployeRepository employeeRepository;(code-box)
@Captor annotation
The @Captor annotation can be used to capture the arguments' values. The @Captor annotations are used with the BDDMocktio class's verify()
method.
The @Captor annotation is commonly used to validate that the same values have been supplied to the method you anticipate when unit testing by simply capturing the value using the BDDMOckito's verify method.
The org.mockito.Captor
package contains the @Captor annotation.
@Captor private ArgumentCaptor<String> name;(code-box)
@Spy annotation
The @Spy annotation may generate a partial fake object, which means that if you're spying on a list object, you can easily add the element to the real object (rather than the mock), and you can control the behavior of the output by stubging it using the when() and then() methods of the BDDMockito class.
The org.mockito.Spy
package contains the @Spy annotation.
@Spy private ArrayList<String> listOfEmployeeName;(code-box)
@InjectMocks annotation
The dependencies are injected into the object using the @InjectMocks
annotation. The objects associated with the annotations @Mock and @Spy will be injected through the @InjectMocks annotation.
Dependency injection in the mockito framework can be achieved by the use of setters, constructor bases, or field injections, often known as property injections.
The @InjectMocks is available in the org.mockito.InjectMocks
package and the following snippet show how to use the @InjectMocks annotation.
@InjectMocks private EmployeeService employeeService;(code-box)
@ExtendWith(MockitoExtension.class) annotation
@ExtendWith
annotation is a class-level JUnit 5 annotation that describes an extension model.. The MockitoExtension.class
is used alongside with the @ExtendWith annotation to interact with JUnit5 where mock objects are initialized and strick stubbing is handled.
The @ExtendWith annotation is comparable to the MockitoJUnitRunner annotation in JUnit 4.
@ExtendWith is available in the org.junit.jupiter.api.extension.ExtendWith
package, and MockitoExtension.class is available in the org.mockito.junit.jupiter.MockitoExtension
package.
The code below demonstrates how to use @ExtendWith(MockitoExtension.class).
@ExtendWith(MockitoExtension.class) class EmployeeServiceTest { }(code-box)
Understand the classes that will be utilized for creating BDD test cases in this lecture.
We have a TransactionService class with the creditCardTransaction method, and we are going to generate test cases using the Mockito framework and behavior-driven development.
Before we go, let's take a short look at the classes utilized in this lesson.
TransactionService class
We are interested about developing BDD (behavior-driven development) unit test cases for the TransactionService class using the mockito framework.
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.exception.TransactionException;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.dto.TransactionStatus;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import java.util.Objects;
public class TransactionService
{
private TransactionRepository transactionRepository;
public TransactionService(TransactionRepository transactionRepository)
{
this.transactionRepository = transactionRepository;
}
public TransactionStatus creditCardTransaction(TransactionDetail transactionDetail)
throws TransactionException
{
if (Objects.isNull(transactionDetail))
throw new TransactionException("transactionDetails must be non null");
boolean uniqueTransactionId = transactionRepository.isUniqueTransactionId(transactionDetail.transactionId());
System.out.println("is uniqueTransactionId : " + uniqueTransactionId);
if (!uniqueTransactionId)
throw new TransactionException("transaction id must be unique");
boolean isTransactionSuccess = completeTransaction(transactionDetail);
if (isTransactionSuccess)
return new TransactionStatus((short) 200, "success");
return new TransactionStatus((short) 501, "fail");
}
private boolean completeTransaction(TransactionDetail transactionDetail)
{
if (transactionDetail.cardNumber() < 0)
return false;
transactionRepository.saveTransaction(transactionDetail);
return true;
}
}
TransactionRepository class
I designed a sample repository, TransactionRepository, with several hardcoded tasks. Typically, we develop the dao layer, which is in charge of interfacing with our database to fetch or save data as needed.
I wanted to show you how we limit the DB call while designing the unit test case since DB calls in the unit test case might be quite expensive for us.
package in.learnjavaskills.mockitotutorial.repository;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
public class TransactionRepository
{
public boolean isUniqueTransactionId(long transactionId)
{
return false;
}
public void saveTransaction(TransactionDetail transactionDetail)
{
System.out.println("transaction saved");
}
}
TransactionDetail and TransactionStatus record
The TransactionDetail and TransactionStatus records are used to accept the request and respond to it.
I'm building an enterprise-level course to cover all topic in the mocktio. We will learn how to check the response and how requests go from one class to another by utilizing these two records, which we will examine later.
package in.learnjavaskills.mockitotutorial.dto;
import java.math.BigDecimal;
public record TransactionDetail(long transactionId, long cardNumber, byte cardExpiryYear,
byte cardExpiryMonth, BigDecimal transactionAmount)
{}
package in.learnjavaskills.mockitotutorial.dto;
public record TransactionStatus(short statusCode, String message)
{}
If you're new to the Java record, I recommend reading What Is a Record in Java?
TransactionException class
This session will go over the in-depth mockito framework, therefore I'd like to demonstrate how quickly we'll be able to identify whether exceptions are raised.
package in.learnjavaskills.mockitotutorial.exception;
public class TransactionException extends RuntimeException
{
public TransactionException(String message)
{
super(message);
}
}
Mock the behavior using the given() and willReturn() methods.
Let's start by mock the transactionRepository class, and then we'll limit the calls to isUniqueTransactionId of the transactionRepository by utilizing the BDDMockito class's given()
and willReturn()
methods.
The transactionRepository class's isUniqueTransactionId method returns false, and that function must return true in order to test the positive case in the creditCardTransaction method.
Therefore, I mocked the transactionRepository class and mocked the isUniqueTransactionId method response with the value true.
To cover the test scenario, the following code from the TransactionService class's creditCardTransaction method must be mocked..
public boolean isUniqueTransactionId(long transactionId)
{
return false;
}
boolean uniqueTransactionId = transactionRepository.isUniqueTransactionId(transactionDetail.transactionId());
Here comes the test case, in which we mock the objects and prevent the actual object's calling by passing our own values to the function.
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.dto.TransactionStatus;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatcher;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class TransactionServiceTest
{
@Mock
private TransactionRepository transactionRepository;
@InjectMocks
private TransactionService transactionService;
private long transactionId = 101L;
private TransactionDetail transactionDetail = new TransactionDetail(transactionId,
1234_5678_9101_1213L, (byte) 23, (byte) 11, BigDecimal.TEN);
@Test
void creditCardTransactionPositiveFlow()
{
BDDMockito.given(transactionRepository.isUniqueTransactionId(transactionId))
.willReturn(true);
TransactionStatus transactionStatus = transactionService.creditCardTransaction(transactionDetail);
short statusCode = transactionStatus.statusCode();
assertEquals(statusCode, 200);
}
}
The when()
and thenReturn()
methods can also be used to mock the behavior. The syntax is illustrated below. given() and willReturn() are frequently preferred over when() and thenReturn() since they seem more BDD.
BDDMockito.when(transactionRepository.isUniqueTransactionId(transactionId))
.thenReturn(true);
What exactly is ArgumentMatchers?
ArgumentMatchers is a class found in the org.mockito
package. ArgumentMatchers provide flexible verification and stubbing of mock behaviors.
If you look closely at our previous example, when we covered mocking the behavior of the TransactionRepository's isUniqueTransactionId function, where we specifically specify the transactionId, you'll notice that
BDDMockito.given(transactionRepository.isUniqueTransactionId(ArgumentMatchers.anyLong()))
.willReturn(true);
We may stub it instead of supplying the precise transactionId by just utilizing the ArgumentMatchers class.
@Test
void creditCardTransactionPositiveFlow()
{
BDDMockito.given(transactionRepository.isUniqueTransactionId(transactionId))
.willReturn(true);
TransactionStatus transactionStatus = transactionService.creditCardTransaction(transactionDetail);
short statusCode = transactionStatus.statusCode();
assertEquals(statusCode, 200);
}
ArgumentMatchers have several advantages
1. We ensure neat, clean, and maintainable test cases by utilizing ArgumentMatchers.
2. ArgumentMatchers enable us to apply BDD by removing hard coded values.
Verify the behavior with the verify() method.
The BDDMockito's verify()
method is used to validate behavior, such as whether a specific method is called at least once or not, and many more.
The BDDMockito class has two verify methods: the first takes two parameters, T mock
and VerificationMode
, while the second accepts just T mock
arguments and, by default, considers testing the mock object to be executed only once.
public static <T> T verify(T mock, VerificationMode mode)
public static <T> T verify(T mock)
Let's look at an example where we can check whether a transaction is being saved into the database by seeing if the saveTransaction() method of the TransactionRepository class has been called once or not.
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.dto.TransactionStatus;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class TransactionServiceTest
{
@Mock
private TransactionRepository transactionRepository;
@InjectMocks
private TransactionService transactionService;
private long transactionId = 101L;
private TransactionDetail transactionDetail = new TransactionDetail(transactionId,
1234_5678_9101_1213L, (byte) 23, (byte) 11, BigDecimal.TEN);
@Test
void verifyTransactionSaved()
{
BDDMockito.given(transactionRepository.isUniqueTransactionId(ArgumentMatchers.anyLong()))
.willReturn(true);
transactionService.creditCardTransaction(transactionDetail);
BDDMockito.verify(transactionRepository, Mockito.times(1))
.saveTransaction(ArgumentMatchers.any());
}
}
VerificationMode
The VerificationMode is nothing except an interface that allows us to check particular behaviors, such as at least once, exactly once, never, and so on.
The org.mockito.verification
package contains the VerificationMode interface.
You may use the table below to learn how to validate particular behaviors.
VerificationMode | Explanation |
---|---|
verify(T mock, Mockito.times(1) |
The times() method accepts one argument, i.e., wantedNumberOfInvocations of type int. This function is used to make sure certain types of behavior happen. |
verify(T mock, Mockito.never()) |
As the method name implies, never() method is used to ensure that the specific behavior does not occur at all. |
verify(T mock, Mockito.atLeastOnce()) |
Use the atLeastOnce() method to ensure that the activity happened just once. |
verify(T mock, Mockito.atLeast(5) |
The atLeast() method takes one int a parameter, minNumberOfInvocations. We may use this method to ensure that the behavior occurs at least a certain number of times. |
verify(T mock, Mockito.only()) |
The only() method may be used to check only one behavior that occurred throughout your test case. |
verify(T mock, Mockito.atMostOnce()) |
To ensure that maximum behavior occurs just once, utilize the atMostOnce() method. |
verify(T mock, Mockito.atMost(6)) |
The atMost() method, like the atLeast() function, needs one argument of type int, i.e. maxNumberOfInvocations, to evaluate the behavior of the maximum number of invocations. |
What exactly is ArgumentCaptor
In order to confirm whether the right values are supplied to the particular method or not, we can capture the argument values using the ArgumentCaptor
class in the org.mockito
package.
There are two approaches available to us in the mockito framework for capturing the argument: the @Captor
annotation or the ArgumentCaptor.forClass(T class)
. The second method is being used in the example below to capture the argument of the saveTransaction() method.
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.dto.TransactionStatus;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class TransactionServiceTest
{
@Mock
private TransactionRepository transactionRepository;
@InjectMocks
private TransactionService transactionService;
private long transactionId = 101L;
private TransactionDetail transactionDetail = new TransactionDetail(transactionId,
1234_5678_9101_1213L, (byte) 23, (byte) 11, BigDecimal.TEN);
@Test
void verifyTransactionSavedWithCorrectValues()
{
BDDMockito.given(transactionRepository.isUniqueTransactionId(ArgumentMatchers.anyLong()))
.willReturn(true);
transactionService.creditCardTransaction(transactionDetail);
ArgumentCaptor<TransactionDetail> transactionDetailArgumentCaptor = ArgumentCaptor.forClass(TransactionDetail.class);
BDDMockito.verify(transactionRepository)
.saveTransaction(transactionDetailArgumentCaptor.capture());
TransactionDetail expectedTransactionDetail = transactionDetailArgumentCaptor.getValue();
assertEquals(transactionDetail.transactionId(), expectedTransactionDetail.transactionId());
assertEquals(transactionDetail.transactionAmount(), expectedTransactionDetail.transactionAmount());
}
}
The argument passed to the specific method can be captured with the assistance of the capture()
method, and the values that have been captured can then be retrieved using getValue()
.
Utilize the @Captor annotation to capture the values of the arguments
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.dto.TransactionStatus;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class TransactionServiceTest
{
@Mock
private TransactionRepository transactionRepository;
@Captor
ArgumentCaptor<TransactionDetail> transactionDetailArgumentCaptor;
@InjectMocks
private TransactionService transactionService;
private long transactionId = 101L;
private TransactionDetail transactionDetail = new TransactionDetail(transactionId,
1234_5678_9101_1213L, (byte) 23, (byte) 11, BigDecimal.TEN);
@Test
void verifyTransactionSavedWithCorrectValues()
{
BDDMockito.given(transactionRepository.isUniqueTransactionId(ArgumentMatchers.anyLong()))
.willReturn(true);
transactionService.creditCardTransaction(transactionDetail);
BDDMockito.verify(transactionRepository)
.saveTransaction(transactionDetailArgumentCaptor.capture());
TransactionDetail expectedTransactionDetail = transactionDetailArgumentCaptor.getValue();
assertEquals(transactionDetail.transactionId(), expectedTransactionDetail.transactionId());
assertEquals(transactionDetail.transactionAmount(), expectedTransactionDetail.transactionAmount());
}
}
A few practical methods from the ArgumentCaptor class
ArgumentCaptor's method | Description |
---|---|
T capture() |
The parameter values are captured using Captor(), which is required for verification. This method internally registers a customized ArgumentMatcher implementation. The argument value is retained by this argument matcher for use when making assertions at a later time.. |
T getValue() |
The argument's captured value is returned by the getValue() method. If the verified method was called more than once, it would return the most recent captured value. |
List<T> getAllValues() |
The getAllValues() function retrieves all of the values that have been captured when the verified method was called more than once. |
<U, S extends U> ArgumentCaptor<U> forClass(Class<S> clazz) |
Create a new ArgumentCaptor. |
Verify to determine if the exception were thrown
If you've noticed, the creditCardTransaction() method of the TransactionService class throws a TransactionException if the method parameter is null.
if (Objects.isNull(transactionDetail))
throw new TransactionException("transactionDetails must be non null");
It's recommended to test your code to determine if it handles exceptions appropriately.
To utilize Assertions.assertThatThrownBy, I used the assertj-core library. The Gradle dependencies are listed below. here.
testImplementation 'org.assertj:assertj-core:3.24.2' (alert-success)
package in.learnjavaskills.mockitotutorial.service;
import in.learnjavaskills.mockitotutorial.dto.TransactionDetail;
import in.learnjavaskills.mockitotutorial.exception.TransactionException;
import in.learnjavaskills.mockitotutorial.repository.TransactionRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TransactionServiceTest
{
@Mock
private TransactionRepository transactionRepository;
@InjectMocks
private TransactionService transactionService;
@Test
void verifyExceptionThrown()
{
Assertions.assertThatThrownBy(()-> { transactionService.creditCardTransaction(null); })
.hasMessageContaining("transactionDetails must be non null")
.isExactlyInstanceOf(TransactionException.class);
}
}
Conclussion
If you want to be a great developer, you must know how to test your code. In this in-depth mockito framework, we learn how to test your code by utilizing test-driven and behavior-driven development using the mockito and JUnit 5 frameworks.
We've gone over many of the mockito framework's capabilities, such as stubbing with the given() and willReturn() methods to test the code's behavior. We then proceeded to the ArgumentMatchers and ArgumentCaptor.
We covered the various methods for verifying method invocations and the verification modes; we distinguished them and knew which verification mode should be used and when.
Finally, we learn how to ensure that excaptions are handled correctly.
Keep learning and keep growing.
(getButton) #text=(Next: How to Mock Void Methods with Mockito) #icon=(link) #color=(#2339bd)