Unit Testing in Java – JUnit, Test Lifecycle, Assertions, and Mocking


Learn how to write unit tests in Java using JUnit, understand the test lifecycle, use assertions for validation, handle exception testing, and leverage Mockito for mocking objects in tests.

Unit testing is a critical part of software development that ensures your code works as expected. In Java, JUnit is the most widely used framework for unit testing, while Mockito is used for mocking dependencies during testing. This tutorial covers the basics of JUnit, the test lifecycle, assertions, testing exceptions, and mocking with Mockito.

1. Introduction to JUnit

JUnit is a framework for writing and running tests in Java. It provides an easy way to create unit tests and check that your methods behave correctly. JUnit is widely used for test-driven development (TDD), and it is a part of the JUnit 5 suite, which includes several improvements over older versions (JUnit 4).

Key Features of JUnit:

  1. Annotations: @Test, @Before, @After, @BeforeAll, @AfterAll for marking test methods and setup/teardown methods.
  2. Assertions: Methods like assertEquals(), assertTrue(), assertFalse() for validating expected results.
  3. Test Suites: Allows grouping of tests into suites.
  4. Parameterized Tests: Allows running the same test with different input values.

Basic Example of a JUnit Test:


import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatorTest {

@Test
public void testAddition() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3)); // Checking if 2 + 3 equals 5
}
}

In this example, the @Test annotation indicates that testAddition() is a test method, and assertEquals() is used to verify that the method produces the expected result.

2. Test Lifecycle in JUnit

The JUnit test lifecycle defines the order in which methods are executed during a test run. It includes setup and teardown steps that run before and after each test, or even before and after all tests in a class.

JUnit 5 Test Lifecycle Annotations:

  1. @BeforeAll: Runs once before all tests in the test class.
  2. @AfterAll: Runs once after all tests in the test class.
  3. @BeforeEach: Runs before each test method.
  4. @AfterEach: Runs after each test method.

Example – Test Lifecycle:


import org.junit.jupiter.api.*;

public class LifecycleExample {

@BeforeAll
static void setupAll() {
System.out.println("Before all tests");
}

@BeforeEach
void setup() {
System.out.println("Before each test");
}

@Test
void testMethod1() {
System.out.println("Test 1 executed");
}

@Test
void testMethod2() {
System.out.println("Test 2 executed");
}

@AfterEach
void tearDown() {
System.out.println("After each test");
}

@AfterAll
static void tearDownAll() {
System.out.println("After all tests");
}
}

Output:


Before all tests
Before each test
Test 1 executed
After each test
Before each test
Test 2 executed
After each test
After all tests

Key Points:

  1. @BeforeAll and @AfterAll run once for the entire test class.
  2. @BeforeEach and @AfterEach run before and after each test method.

3. Assertions in JUnit

Assertions are used in unit tests to verify that the actual result matches the expected result. JUnit provides several assertion methods to perform different types of checks.

Common Assertion Methods:

  1. assertEquals(expected, actual): Checks if two values are equal.
  2. assertTrue(condition): Checks if a condition is true.
  3. assertFalse(condition): Checks if a condition is false.
  4. assertNotNull(object): Checks if an object is not null.
  5. assertNull(object): Checks if an object is null.

Example – Assertions:


import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class AssertionExample {

@Test
void testAssertions() {
assertEquals(10, 5 + 5); // Check if 5 + 5 equals 10
assertTrue(5 > 0); // Check if 5 is greater than 0
assertFalse(3 < 0); // Check if 3 is not less than 0
assertNotNull("Hello"); // Check if the string is not null
assertNull(null); // Check if the object is null
}
}

Key Points:

  1. Use assertions to validate your test outcomes.
  2. assertEquals is often used to compare expected and actual values in tests.
  3. assertTrue and assertFalse are used for boolean conditions.

4. Testing Exceptions in JUnit

Testing exceptions is important when you expect your code to throw certain exceptions during execution. JUnit provides annotations and assertion methods to handle this.

JUnit 5 Exception Testing:

To test if a method throws an exception, you can use the assertThrows() method.

Example – Testing Exceptions:


import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class ExceptionTest {

@Test
void testException() {
Exception exception = assertThrows(ArithmeticException.class, () -> {
int result = 10 / 0;
});
assertEquals("/ by zero", exception.getMessage());
}
}

Key Points:

  1. assertThrows(): Used to verify that a specific exception is thrown during the test.
  2. You can assert the exception's message using getMessage().

5. Mocking with Mockito

Mockito is a popular mocking framework in Java used for unit testing. It allows you to mock objects, simulate their behavior, and validate interactions with them.

Key Features of Mockito:

  1. Mock Objects: Create mock objects that simulate the behavior of real objects.
  2. Stub Methods: Define the behavior of methods in the mock object.
  3. Verify Interactions: Verify that certain methods were called on the mock object.

Basic Example – Mocking with Mockito:


import org.junit.jupiter.api.*;
import static org.mockito.Mockito.*;

public class MockitoExample {

@Test
void testMockito() {
// Create a mock object of the Calculator class
Calculator calculatorMock = mock(Calculator.class);
// Define the behavior of the add method
when(calculatorMock.add(2, 3)).thenReturn(5);
// Call the method
int result = calculatorMock.add(2, 3);
// Verify the result
assertEquals(5, result);
// Verify that the method was called once
verify(calculatorMock).add(2, 3);
}
}

Key Mockito Methods:

  1. mock(Class<T> classToMock): Creates a mock object of the given class.
  2. when(...).thenReturn(...): Defines the behavior of a method in the mock.
  3. verify(...): Verifies that a method was called on the mock.