Java Unit Testing with Mockito Example
In this post, we feature a comprehensive article about Java Unit Testing with Mockito Example.
1. Introduction
Java unit testing is a software testing which focuses on testing methods in a class. When a class depends on other classes and/or interfaces, we can test it by using Mockito to create and configure mock objects.
In this example, first, I will create a class which depends on an interface and other class. Second, I will test it using Mockito with Junit framework. I will demonstrate the following actions:
- Define a mocked object with
@Mock
. - Define a test class which depends on mocked objects with
@InjectMocks
. - Configure a mocked object’s behavior.
- Verify a mocked object’s behavior.
2. Technologies Used
The example code in this article was built and run using:
- Java 11
- Maven 3.3.9
- Eclipse Oxygen
- Junit 4.12 and 5.5.2
3. Maven Project
In this step, I will demonstrate how to use Mockito in both JUnit 4 and JUnit 5 in a three-module Maven project:
common
– includes four classes –SomeClass
,OtherService
,SomeInterface
, andSomeException
.SomeClass
depends onOtherService
andSomeInterface
.JUnit4-demo
– testsSomeClass
with Mockito in JUnit 4.JUnit5-demo
– testsSomeClass
with Mockito in JUnit 5.
3.1 Dependencies
Parent pom.xml includes three modules.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jcg.zheng.demo</groupId> <artifactId>junit-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <name>junit-demo</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <description>parent project for junit demo</description> <modules> <module>common</module> <module>junit4-demo</module> <module>junit5-demo</module> </modules> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <release>11</release> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M4</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.8.2</version> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-report-plugin</artifactId> <version>3.0.0-M4</version> </plugin> </plugins> </reporting> </project>
4. Common Module
In this step, I will create a common module which contains four classes:
OtherService
– a class which has three public methods.SomeInterface
– an interface which defines two public methods.SomeException
– a run time exceptionSomeClass
– has four methods and depends on bothOtherService
andSomeInterface
.
4.1 POM
The common
module’s pom.xml is defined as the following:
pom.xml
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>jcg.zheng.demo</groupId> <artifactId>junit-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>common</artifactId> <name>comon</name> <url>http://maven.apache.org</url> </project>
4.2 SomeInterface
In this step, I will create SomeInterface
which has the following methods:
doSomething
– it does not return anything.getSomeList
– it returns a list of string values.
SomeInterface.java
package jcg.zheng.demo; import java.util.List; public interface SomeInterface { void doSomething(String input); List<String> getSomeList(String input); }
4.3 OtherService
In this step, I will create OtherService
.
OtherService.java
package jcg.zheng.demo; public class OtherService { public int calculateByDoubleIt(int num) { return num * 2; } public void doSomething(String input) { if ("Ok".equalsIgnoreCase(input)) { System.out.println("do something."); } else { throw new RuntimeException("Remote service failed"); } } public boolean returnABoolean(String inputData) { if ("Save".equalsIgnoreCase(inputData)) { return true; } else { return false; } } }
4.4 SomeException
In this step, I will create a SomeException
class which extends from RuntimeExcpetion
.
SomeException.java
package jcg.zheng.demo; public class SomeException extends RuntimeException { private static final long serialVersionUID = 347808963459470775L; public SomeException(String msg) { super(msg); } }
4.5 SomeClass
In this step, I will create a SomeClass
which depends on both SomeInterface
and OtherService
. It has the following methods:
doubleANumber
– returns an integer after invokingsomeService.calculateByDoubleIt
.processData
– returnsvoid
. It depends onsomeInterface.getSomeList
andsomeService.doSomething
returnABoolean
– returns aBoolean
value after callingsomeService.returnABoolean
voidFoo
– returnsvoid
. It depends onsomeInterface.doSomething
andsomeService.doSomething
.
SomeClass.java
package jcg.zheng.demo; import java.util.List; public class SomeClass { private SomeInterface someInterface; private OtherService someService; public int doubleANumber(int num) { try { return someService.calculateByDoubleIt(num); } catch (Exception e) { throw new SomeException("External Service-calculateByDoubleIt failed " + e.getMessage()); } } public void processData(String input) { List<String> rawList = someInterface.getSomeList(input); if (rawList != null) { for (String item : rawList) { someService.doSomething(item); } } } public boolean returnABoolean(String inputData) { try { return someService.returnABoolean(inputData); } catch (Exception e) { throw new SomeException("External Service-returnABoolean failed " + e.getMessage()); } } public void voidFoo(String inputData) { try { if ("Interface".equalsIgnoreCase(inputData)) { someInterface.doSomething(inputData); } someService.doSomething(inputData); } catch (Exception e) { throw new SomeException("External Service-doSomething failed " + e.getMessage()); } } }
5. JUnit 4 Test
In this step, I will create a JUnit 4 test class to test SomeClass
with mocked SomeInterface
and OtherService
.
5.1 POM
The JUnit4-demo
module’s pom.xml
and depends on mockito-core
and the common
module.
pom.xml
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>jcg.zheng.demo</groupId> <artifactId>junit-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>junit4-demo</artifactId> <name>junit4-demo</name> <url>http://maven.apache.org</url> <properties> <junit.version>4.12</junit.version> </properties> <dependencies> <dependency> <groupId>jcg.zheng.demo</groupId> <artifactId>common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.2.4</version> <scope>test</scope> </dependency> </dependencies> </project>
5.2 SomeClassTest
In this step, I will create a SomeClassTest
class in JUnit 4. It will use the following Mockito annotation and libraries:
- @Mock – marks a field as a mock object.
- @InjectMocks – marks a field on which injection should be performed.
- @RunWith(MockitoJUnitRunner.class) – Integrates with Junit 4 runner
- Mockito static methods:
when
,doThrow
,times
, andverify
.
SomeClassTest.java
package jcg.zheng.demo.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import jcg.zheng.demo.SomeException; import jcg.zheng.demo.SomeClass; import jcg.zheng.demo.OtherService; import jcg.zheng.demo.SomeInterface; @RunWith(MockitoJUnitRunner.class) public class SomeClassTest { @InjectMocks private SomeClass classUnderTest; @Mock private SomeInterface mockedInterface; @Mock private OtherService mockedService; @Test public void test_doubleANumber() { when(mockedService.calculateByDoubleIt(3)).thenReturn(6); assertEquals(6, classUnderTest.doubleANumber(3)); verify(mockedService, times(1)).calculateByDoubleIt(3); } @Test(expected = SomeException.class) public void test_doubleANumber_exception() { when(mockedService.calculateByDoubleIt(3)).thenThrow(NullPointerException.class); classUnderTest.doubleANumber(3); verify(mockedService, times(1)).calculateByDoubleIt(3); } @Test public void test_processData() { when(mockedInterface.getSomeList(anyString())).thenReturn(null); classUnderTest.processData("NA"); verify(mockedInterface, times(1)).getSomeList(anyString()); verify(mockedService, times(0)).doSomething(anyString()); } @Test public void test_processData_2() { List<String> twoItemsList = Arrays.asList("Mary", "Zheng"); when(mockedInterface.getSomeList(anyString())).thenReturn(twoItemsList); classUnderTest.processData("NA"); verify(mockedInterface, times(1)).getSomeList(anyString()); verify(mockedService, times(2)).doSomething(anyString()); } @Test(expected = SomeException.class) public void test_returnBooleanFoo_exception() { when(mockedService.returnABoolean("NA")).thenThrow(NullPointerException.class); classUnderTest.returnABoolean("NA"); verify(mockedService, times(1)).returnABoolean("NA"); } @Test public void test_returnBooleanFoo_false() { when(mockedService.returnABoolean("NA")).thenReturn(false); boolean shouldReturnFalse = classUnderTest.returnABoolean("NA"); assertFalse(shouldReturnFalse); } @Test public void test_returnBooleanFoo_true() { when(mockedService.returnABoolean("Save")).thenReturn(true); boolean shouldReturnTrue = classUnderTest.returnABoolean("Save"); assertTrue(shouldReturnTrue); verify(mockedService, times(1)).returnABoolean("Save"); } @Test public void test_voidFoo() throws IllegalAccessException { try { classUnderTest.voidFoo("Ok"); verify(mockedService, times(1)).doSomething("Ok"); verify(mockedInterface, times(0)).doSomething(anyString()); } catch (Exception e) { fail("Should not throw exception"); } } @Test(expected = SomeException.class) public void test_voidFoo_exception() { doThrow(IllegalStateException.class).when(mockedService).doSomething("NA"); classUnderTest.voidFoo("NA"); } @Test public void test_voidFoo_interface() throws IllegalAccessException { try { classUnderTest.voidFoo("Interface"); verify(mockedService, times(1)).doSomething("Interface"); verify(mockedInterface, times(1)).doSomething("Interface"); } catch (Exception e) { fail("Should not throw exception"); } } }
Output
[INFO] Running jcg.zheng.demo.junit4.SomeClassTest [INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.43 s - in jcg.zheng.demo.junit4.SomeClassTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
6. JUnit 5 Module
In this step, I will create a JUnit 5 test class to test SomeClass
with mocked objects.
6.1 POM
The JUnit5-demo
module’s pom.xml
depends on JUnit 5 and common modules. Please note that it includes: junit-jupiter-engine
,junit-jupiter-api
, mockito-core
, and mockito-junit-jupiter
.
pom.xml
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>jcg.zheng.demo</groupId> <artifactId>junit-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>junit5-demo</artifactId> <name>junit5-demo</name> <url>http://maven.apache.org</url> <properties> <junit-jupiter.version>5.5.2</junit-jupiter.version> </properties> <dependencies> <dependency> <groupId>jcg.zheng.demo</groupId> <artifactId>common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-runner</artifactId> <version>1.5.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.2.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>2.23.0</version> <scope>test</scope> </dependency> </dependencies> </project>
6.2 SomeClassTest
In this step, I will create a SomeClassTest
class in JUnit 5. It has the same annotations as Junit 4, the only difference is Junit 5 uses @ExtendWith(MockitoExtension.class)
.
SomeClassTest.java
package jcg.zheng.demo.junit5; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import jcg.zheng.demo.SomeException; import jcg.zheng.demo.SomeClass; import jcg.zheng.demo.OtherService; import jcg.zheng.demo.SomeInterface; @ExtendWith(MockitoExtension.class) public class SomeClassTest { @InjectMocks private SomeClass classUnderTest; @Mock private SomeInterface mockedInterface; @Mock private OtherService mockedService; @Test @DisplayName("It should double a number") public void test_doubleANumber() { when(mockedService.calculateByDoubleIt(3)).thenReturn(6); assertEquals(6, classUnderTest.doubleANumber(3)); verify(mockedService, times(1)).calculateByDoubleIt(3); } @Test public void test_doubleANumber_exception() { when(mockedService.calculateByDoubleIt(3)).thenThrow(NullPointerException.class); assertThrows(SomeException.class, () -> { classUnderTest.doubleANumber(3); }); verify(mockedService, times(1)).calculateByDoubleIt(3); } @Test public void test_processData() { when(mockedInterface.getSomeList(anyString())).thenReturn(null); classUnderTest.processData("NA"); verify(mockedInterface, times(1)).getSomeList(anyString()); verify(mockedService, times(0)).doSomething(anyString()); } @Test public void test_processData_2() { List<String> twoItemsList = Arrays.asList("Mary", "Zheng"); when(mockedInterface.getSomeList(anyString())).thenReturn(twoItemsList); classUnderTest.processData("NA"); verify(mockedInterface, times(1)).getSomeList(anyString()); verify(mockedService, times(2)).doSomething(anyString()); } @Test public void test_returnBooleanFoo_exception() { when(mockedService.returnABoolean("NA")).thenThrow(NullPointerException.class); assertThrows(SomeException.class, () -> { classUnderTest.returnABoolean("NA"); }); verify(mockedService, times(1)).returnABoolean("NA"); } @Test public void test_returnBooleanFoo_false() { when(mockedService.returnABoolean("NA")).thenReturn(false); boolean shouldReturnFalse = classUnderTest.returnABoolean("NA"); assertFalse(shouldReturnFalse); } @Test public void test_returnBooleanFoo_true() { when(mockedService.returnABoolean("Save")).thenReturn(true); boolean shouldReturnTrue = classUnderTest.returnABoolean("Save"); assertTrue(shouldReturnTrue); verify(mockedService, times(1)).returnABoolean("Save"); } @Test public void test_voidFoo() throws IllegalAccessException { classUnderTest.voidFoo("OK"); verify(mockedService, times(1)).doSomething("OK"); } @Test public void test_voidFoo_exception() { doThrow(IllegalStateException.class).when(mockedService).doSomething("NA"); assertThrows(SomeException.class, () -> { classUnderTest.voidFoo("NA"); }); } @Test public void test_voidFoo_interface() throws IllegalAccessException { classUnderTest.voidFoo("Interface"); verify(mockedService, times(1)).doSomething("Interface"); verify(mockedInterface, times(1)).doSomething("Interface"); } }
Output
[INFO] Running jcg.zheng.demo.junit5.SomeClassTest [INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.042 s - in jcg.zheng.demo.junit5.SomeClassTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
7. Summary
In this example, I demonstrated how to use Mockito to
- Create mocked objects with
@Mock
. - Inject the mocked objects for the testing class marked with
@InjectMocks
. - Use Mockito
static
method to mock the object’s behavior. - Verify the mocked object’s method is invoked.
8. Download the Source Code
You can download the full source code of this example here: Java Unit Testing With Mokito Example