JUnit
assertEquals
In JUnit,
assertEquals
is a method used to check if the expected value and the actual value are equal in unit tests.
SHOW CODE
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class UnitTest {
@Test
void addition() {
Calculator calculator = new Calculator();
int result = calculator.add(1, 1);
assertEquals(2, result, "The result of 1 + 1 should be 2");
}
}
assertTure & assertFalse
In JUnit, the
assertTrue
and theassertFalse
methods are used to validate boolean expressions in unit tests.
SHOW CODE
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class UnitTest {
@Test
void testIsOdd() {
int n = 5;
assertTrue(n % 2 == 1, "Number should be an odd");
assertFalse(n % 2 == 0, "Number should not be an even");
}
}
@BeforeEach & @AfterEach
In JUnit, the
@BeforeEach
and@AfterEach
annotations are used to specify methods that should run before or after each test method, respectively. These annotations are useful for setting up resources before each test and cleaning up resources after each test.
SHOW CODE
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class UnitTest {
private Calculator calculator;
@BeforeEach
void setup() {
calculator = new Calculator();
}
@Test
void testAddition() {
assertEquals(2, calculator.add(1, 1),
"The result of 1 + 1 should be 2.");
}
@AfterEach
void teardown() {
calculator = null;
}
}
@BeforeAll & @AfterAll
In JUnit, the
@BeforeAll
and@AfterAll
annotations are used to specify methods that run once before and after all the test methods in a test class, respectively. These methods are commonly used for expensive setup and cleanup.
SHOW CODE
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class UnitTest {
@BeforeAll
public static void setUpBeforeClass() {
System.out.println("Executed before all tests are run.");
}
@AfterAll
public static void tearDownAfterClass() {
System.out.println("Executed after all tests are run.");
}
@Test
public void testMethod1() {
System.out.println("Test 1 executed.");
}
@Test
public void testMethod2() {
System.out.println("Test 2 executed.");
}
}
SHOW OUTPUT
Executed before all tests are run.
Test 1 executed.
Test 2 executed.
Executed after all tests are run.
assertArrayEquals & assertNotEquals
In JUnit, the
assertArrayEquals
andassertNotEquals
methods are used to check if both arrays are equal in sizeand whether each corresponding element is equal or not. For arrays containing objects, JUnit will use theequals
method of the objects for the comparison.
SHOW CODE
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class UnitTest {
@Test
void testArrayEquality() {
int[] expected = {1, 2, 3};
int[] actual = {1, 2, 3};
assertArrayEquals(expected, actual, "Arrays should be equal");
String[] expected2 = {"apple", "banana", "cherry"};
String[] actual2 = {"apple", "banana", "cherry"};
// Compare arrays of objects (Strings in this case)
assertArrayEquals(expected2, actual2);
}
@Test
void testArrayNotEquality() {
int[] expected = {1, 2, 3};
int[] actual = {1, 2, 4};
assertNotEquals(expected, actual, "Arrays should not be equal");
}
}
assertThrows
In Junit, the
assertThrows
is a method used to assert that a specific exception is throwing during the execution of a piece of code.
SHOW CODE
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class UnitTest {
@Test
void testDivision() {
Calculator calculator = new Calculator();
ArithmeticException exception = assertThrows(ArithmeticException.class, () -> {
calculator.divide(1, 0);
});
assertEquals("/ by zero", exception.getMessage());
}
}
assertTimeout & assertTimeoutPreemptively
In JUnit, the
assertTimeout
andassertTimeoutPreemptively
methods are used to ensure that a unit test finishes within a specified time limit. The key difference between them is that theassertTimeoutPreemptively
method will terminate the test as soon as the excution time exceeds the specified limit. In contrast, theassertTimeout
method allows the test to complete before checking if the time limit was exceeded.
SHOW CODE
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
public class UnitTest {
@Test
void testExecutionWithinTimeoutPreemptively() {
assertTimeoutPreemptively(Duration.ofMillis(1000), () -> {
// Simulate a task that takes more time than allowed
Thread.sleep(1500); // Task that takes more than 1000ms
});
}
@Test
void testExecutionWithinTimeout() {
assertTimeout(Duration.ofMillis(1000), () -> {
// Simulate a task that takes some time
Thread.sleep(500); // Task that takes less than 1000ms
});
}
}
Parameterized Tests
In JUnit, Parameterized Tests allow a test to be run with different parameters and expected results, which reduces repetitive code and increases test coverage. Common parameter sources include
@ValueSource
, which passes a group of values to the test;@CsvSource
, which allows passing multiple values separated by commmas;@MethodSource
, which generates values dynamically from a method; and@EnumSource
, which passes enum constants from an enum class.
SHOW CODE
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class UnitTest {
@ParameterizedTest
@ValueSource(strings = {"apple", "banana", "cherry"})
void testWithStringParameter(String fruit) {
assertTrue(fruit.length() > 3);
}
@ParameterizedTest
@CsvSource({
"apple, 5",
"banana, 6",
"cherry, 6"
})
void testWithCsvSource(String fruit, int length) {
assertEquals(fruit.length(), length);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana", "cherry");
}
@ParameterizedTest
@MethodSource("stringProvider")
void testWithMethodSource(String fruit) {
assertTrue(fruit.length() > 3);
}
enum FRUIT {APPLE, BANANA, CHERRY}
@ParameterizedTest
@EnumSource(FRUIT.class)
void testWithEnumSource(FRUIT fruit) {
assertTrue(fruit.name().length() > 3);
}
}
Mockito
System Under Test
The System Under Test (SUT) refers to the specific part of the software being tested in a given test case. It can range from a single method in unit testing to larger components in integration or system testing based on different context. The SUT is typically isolated using mocking and stubbing to test its behavior independently.
SHOW CODE
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
public class CalculatorTest {
@Test
void testAdd() {
Calculator calculator = new Calculator(); // SUT
int result = calculator.add(2, 3); // The add method is the System Under Test
assertEquals(5, result);
}
}
Mocking vs. Stubbing
In unit testing, Mocking and Stubbing are used to simulate and control the behavior of external dependencies. Mocking is commonly used for behavior verification, where objects are created to simulate the behavior of real-world components in a controlled manner. On the other hand, Stubbing is typically used to isolate the SUT by controlling the return values or responses from dependencies.
SHOW CODE: Mocking
public class Calculator {
private Logger logger;
public Calculator(Logger logger) {
this.logger = logger;
}
public int add(int a, int b) {
int result = a + b;
logger.log("Addition performed: " + result);
return result;
}
}
@Test
void testAdd() {
Logger mockLogger = mock(Logger.class);
Calculator calculator = new Calculator(mockLogger);
calculator.add(2, 3);
// Verifying the interaction with the mock: was log method called?
verify(mockLogger).log("Addition performed: 5");
}
SHOW CODE: Stubbing
public class Calculator {
private DatabaseService databaseService;
public Calculator(DatabaseService databaseService) {
this.databaseService = databaseService;
}
public int getProductPrice(int productId) {
Product product = databaseService.getProduct(productId);
return product.getPrice();
}
}
@Test
void testGetProductPrice() {
// Stub the DatabaseService to return a fixed product
DatabaseService stubbedService = mock(DatabaseService.class);
Product stubProduct = new Product(1, "Laptop", 1000);
when(stubbedService.getProduct(1)).thenReturn(stubProduct);
Calculator calculator = new Calculator(stubbedService);
int price = calculator.getProductPrice(1);
assertEquals(1000, price);
}
mock(), when().thenReturn(), verify()
In Mockito, the
mock()
method is used to create a mock object, which allows simulating the behavior of an object without invoking its real methods. Thewhen()
method is used to define the behavior of a mock object’s method when it is called with specific arguments. ThethenReturn()
method specifies the return value of a mocked method. Theverify()
method is used to check whether a specified method on a mock object was called during the test execution.
SHOW CODE
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
public class UnitTest {
@Test
public void testMockingBehavior() {
// Create a mock List object
List<String> mockList = mock(List.class);
// Specify behavior for when add() is called
when(mockList.add("Hello")).thenReturn(true); // Return true when "Hello" is added
// Call the mock method
boolean result = mockList.add("Hello");
// Verify the behavior
assertTrue(result); // The add method should return true
verify(mockList).add("Hello"); // Verify that add("Hello") was called
// Verify that add("World") was never called
verify(mockList, never()).add("World");
}
}
Argument Matchers
In Mockito, argument matchers are used to specify conditions for method arguments when stubbing methods. They are commonly used in the
when()
andthenReturn()
methods when the exact method argument is not critical. Some commonly used argument marchers include:
any()
: Matches any argument of the specified type.eq()
: Matches a specified argument.anyString()
: Matches any argument of typeString
.anyInt()
: Matches any argument of typeInteger
.isA()
: Matches an argument of a specified class type.argThat()
: Allows for custom argument matching logic.
SHOW CODE
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.*;
public class UnitTest {
@Test
public void testAnyMatcher() {
List mockList = mock(List.class);
// Stub behavior using argument matcher
when(mockList.get(anyInt())).thenReturn("Signal");
// Verify that any argument passed to get will return "Signal"
assertEquals("Signal", mockList.get(9));
assertEquals("Signal", mockList.get(999));
}
@Test
public void testEqMatcher() {
Calculator calculator = mock(Calculator.class);
when(calculator.add(eq(1), eq(1))).thenReturn(2);
assertEquals(2, calculator.add(1, 1));
assertNotEquals(3, calculator.add(1, 1));
}
@Test
public void testAnyStringMatcher() {
// Create a mock List
List<String> mockList = mock(List.class);
// Stub the method to return "Hello" for any string argument
when(mockList.contains(anyString())).thenReturn(true);
// Verify that the mock returns true for any string passed as an argument
assertTrue(mockList.contains("Test")); // Matches any string
assertTrue(mockList.contains("Hello")); // Matches any string
assertTrue(mockList.contains("World")); // Matches any string
}
@Test
public void testAnyIntMatcher() {
// Create a mock List
List mockList = mock(List.class);
// Stub the method to return "Found" for any integer argument
when(mockList.get(anyInt())).thenReturn("Found");
// Verify that the mock returns "Found" regardless of the argument
assertEquals("Found", mockList.get(1)); // Matches any integer
assertEquals("Found", mockList.get(999)); // Matches any integer
}
@Test
public void testArgThatMatcher() {
// Create a mock List
List<String> mockList = mock(List.class);
// Use argThat to match arguments that are not null and have length greater than 3
when(mockList.add(argThat(argument ->
argument != null && argument.length() > 3)))
.thenReturn(true);
// Verify that the mock behaves correctly
assertTrue(mockList.add("Hello")); // Matches: length > 3
assertFalse(mockList.add("")); // Does not match: empty string
}
}
Essential Mockito Annotaitons
The key annotations in Mockito are
@Mock
,@InjectMocks
,@ExtendWith
, and@Captor
. The@Mock
annotation is used to create mock objects. The@InjectMocks
annotation injects the objects, created with@Mock
, into the specified class under test. The@ExtendWith
annotation specifies a custom test runner to use when executing the test. The@Captor
annotation is used to create an ArgumentCaptor instance, which captures and verifies the arguments passed to mocked methods.
SHOW CODE
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class UnitTest {
@Mock
private List<String> mockList; // Mocked dependency
@InjectMocks
private MyClass myClass; // Class under test
@Captor
private ArgumentCaptor<String> captor; // Captures arguments passed to the mock
@Test
void testAddItem() {
// Perform the action under test
myClass.addItem("Test Item");
// Verify that the mockList's add method was called with the argument "Test Item"
verify(mockList).add(captor.capture());
// Capture and assert the argument
String capturedArgument = captor.getValue();
System.out.println("capturedArgument = " + capturedArgument);
// Assert the captured item is correct
assertEquals("Test Item", capturedArgument);
}
static class MyClass {
private final List<String> list;
public MyClass(List<String> list) {
this.list = list;
}
public void addItem(String item) {
list.add(item);
}
}
}
Mockit Spy
Mockito’s
spy
is a partial mock, typically created using@Spy
, where real methods are used by default, and only the specified methods are stubbed.
SHOW CODE
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class UnitTest {
// Create a spy for the class under test
@Spy
private MyClass myClass = new MyClass();
// Inject the mock dependency into the class under test
@Mock
private List<String> mockList;
@Test
public void testAddItemWithSpy() {
// Use the real method to add an item
myClass.addItem("Test Item");
// Verify that the mock's addItem method is called with the argument
// Fail the test: Actually, there were zero interactions with this mock.
verify(mockList).add("Test Item");
// Stub the method for testing
doReturn("Mocked Response").when(myClass).getGreeting();
// Now the method getGreeting() will return the mocked response
String response = myClass.getGreeting();
System.out.println(response); // Output: Mocked Response
// Assert the mocked behavior
assertEquals("Mocked Response", response);
}
// Class under test
static class MyClass {
private final List<String> list = new ArrayList<>();
public void addItem(String item) {
list.add(item);
}
public String getGreeting() {
return "Hello, World!";
}
}
}
In the above code, the statement
verify(mockList).add("Test Item");
will fail becausemockList
is a partial mock. By default, real methods are called on a spy object, and since the real method is not stubbed or tracked. the verification cannot succeed.
Mockito and SpringBoot Example
SHOW CODE: 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>dev.signalyu</groupId>
<artifactId>springtest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springtest</name>
<description>springtest</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.4.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-commons -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-compiler-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </path>-->
<!-- </annotationProcessorPaths>-->
<!-- </configuration>-->
<!-- </plugin>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <excludes>-->
<!-- <exclude>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </exclude>-->
<!-- </excludes>-->
<!-- </configuration>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
</project>
SHOW CODE
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Data
class User {
private Long id;
private String name;
}
@Repository
interface UserRepository extends CrudRepository<User, Long> {
}
@Service
@Data
class UserService {
@Autowired
private final UserRepository userRepository;
public String getUserById(Long id) {
User user = userRepository.findById(id).orElse(null);
return user == null ? "User not found" : user.getName();
}
}
@RestController
@AllArgsConstructor
class UserController {
private final UserService userService;
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
@SpringBootTest
@AutoConfigureMockMvc
public class UnitTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private UserService userService;
@Test
public void testGetUser() throws Exception {
Long userId = 1L;
String mockResponse = "Signal Yu";
when(userService.getUserById(userId)).thenReturn(mockResponse);
mockMvc.perform(get("/user/{id}", userId))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.content().string(mockResponse));
}
}