#
Review No. 9 (JUnit, Mockito, etc)
This page contains Java concepts briefly explained.
#
JUnit 4 vs JUnit 5
- JUnit 4 -> Java 5 +
- JUnit 5 (modular approach) -> Java 8 +
#
@BeforeEach vs @BeforeAll
- @BeforeEach -> Runs multiple times — once before each test method.
- @BeforeAll -> Runs multiple times — once before each test method.
Info
The method annotated with @BeforeAll (JUnit 5 only) must be static
(unless using a @TestInstance
of TestInstance.Lifecycle.PER_CLASS).
#
Can we disable a test in JUnit 5 ?
Yes, using the @Disabled annotation. Optionally, provide a reason: @Disabled("Reason for disabling").
#
What is @Nested in JUnit 5 ?
@Nested is an annotation in JUnit 5 that allows you to create nested test classes within a test class (used at subclass level).
#
What is @ParameterizedTest annotation used for ?
@ParameterizedTest
is an annotation in JUnit 5 that allows you to run the same test
multiple times with different parameters. It’s particularly useful when you want to test a
method with a variety of inputs without duplicating the test code.
#
Why we need to mark a method with @Test ?
@Test is used to inform the JUnit framework that the annotated method is a
test case that should be executed as part of the test suite. Without @Test:
The method would be treated as a regular method and would not be executed
automatically during the test run.
#
How can we handle exceptions in JUnit 5 ?
Using assertThrows
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ExampleTest {
@Test
void testException() {
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Invalid argument");
});
}
}
- validating the Exception Message
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ExampleTest {
@Test
void testExceptionMessage() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Invalid argument");
});
assertEquals("Invalid argument", exception.getMessage());
}
}
- using assertDoesNotThrow
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class ExampleTest {
@Test
void testNoException() {
assertDoesNotThrow(() -> {
// Code that should not throw any exceptions
int result = 10 / 2;
});
}
}
#
MockitoExtension vs SpringExtension
#
@EnabledIf or @DisabledIf
Used for writing test cases for methods that should run only if certain conditions are met.
#
Why use @Tag annotation ?
The @Tag annotation in JUnit 5 allows you to categorize and organize test methods.
Example:
@Test
@Tag("integration")
@Tag("database")
void testDatabaseIntegration() {
// Test logic involving database integration
}
You can specify tags when running tests via Maven, Gradle, or other build tools.
mvn test -Dgroups="fast"
./gradlew test --tests * --tags fast
#
@TestInstance
By default, JUnit creates a new test instance for each test method (PER_METHOD lifecycle). However, with @TestInstance, you can change this behavior to use a single instance for all test methods (PER_CLASS lifecycle).
Example:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PerClassLifecycleTest {
private int counter = 0;
@Test
void testIncrement1() {
counter++;
assertEquals(1, counter); // First test, counter is 1
}
@Test
void testIncrement2() {
counter++;
assertEquals(2, counter); // Second test, counter is 2
}
}
#
@DisplayName
@DisplayName allows you to provide a custom, human-readable name for your test methods, improving readability and making test reports more understandable.
#
How can you ensure tests are run in a specific order ?
JUnit 5 does not guarantee the order of test execution by default.
@TestMethodOrder
annotation can be used to specify the order of the tests.
Example:
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTest {
@Test
@Order(1)
void testA() {
System.out.println("Running testA");
assertTrue(true);
}
@Test
@Order(2)
void testB() {
System.out.println("Running testB");
assertTrue(true);
}
}
#
What is the difference between assertAll and regular assertions ?
Example:
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
assertAll("group of assertions",
() -> assertEquals(2, 1 + 1, "Addition check"),
() -> assertTrue(5 > 2, "Greater than check"),
() -> assertEquals("hello", "hello", "String equality")
);
Using assertAll we can see the result of all tests in the group.
#
How do you handle timeout in JUnit 5 ?
assertTimeout(Duration.ofMillis(100), () -> {
// Code that should complete within 100 milliseconds
});
assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
// Code that should complete within 1 second
});
assertTimeout
: If the timeout is exceeded, the method waits for the code to complete before failing the test.assertTimeoutPreemptively
: If the timeout is exceeded, the method stops and the test is not passed.
#
What is MockMvc ?
MockMvc is a utility provided by Spring Framework for testing Spring MVC web applications, including controllers & Spring Security.
To use MockMvc, you typically annotate the test class with:
- @WebMvcTest
- @SpringBootTest and @AutoConfigureMockMvc
Info
- @WebMvcTest -> Do not loads the full application context and is used for unit tests.
- @SpringBootTest -> Loads the full application context and is used for more comprehensive integration tests. We need to use @AutoConfigureMockMvc in order to get the MockMvc bean available in the testing application context.
#
Examples using MockMvc
Controller with Path Variable
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenGetUserWithId_thenReturnsUserId() throws Exception {
mockMvc.perform(get("/user/1"))
.andExpect(status().isOk())
.andExpect(content().string("User ID: 1"));
}
}
Controller Handling JSON Requests and Responses
@WebMvcTest(ApiController.class)
public class ApiControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenPostData_thenReturnsProcessedData() throws Exception {
mockMvc.perform(post("/api/data")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"key\":\"value\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.response").value("Received: value"));
}
}
Controller with Spring Security
@WebMvcTest(SecureController.class)
public class SecureControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenUnauthenticated_thenRedirectToLogin() throws Exception {
mockMvc.perform(get("/secure"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
@Test
@WithMockUser(username = "user", roles = {"USER"})
public void whenAuthenticated_thenAccessGranted() throws Exception {
mockMvc.perform(get("/secure"))
.andExpect(status().isOk())
.andExpect(content().string("Secure Content"));
}
}
Controller with Exception Handling
@WebMvcTest(ExceptionController.class)
public class ExceptionControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenExceptionThrown_thenHandled() throws Exception {
mockMvc.perform(get("/exception"))
.andExpect(status().isInternalServerError())
.andExpect(content().string("Handled Exception: Exception occurred"));
}
}
Controller with Dependency Injection
@WebMvcTest(ServiceController.class)
public class ServiceControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService;
@Test
public void whenServiceCalled_thenReturnsMockedData() throws Exception {
// Given
when(myService.getData()).thenReturn("Mocked Service Data");
// When
mockMvc.perform(get("/service"))
//Then
.andExpect(status().isOk())
.andExpect(content().string("Mocked Service Data"));
}
}
#
How could I test a @Repository bean in Spring ?
- Using @DataJpaTest for JPA Repositories
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void whenSaveUser_thenFindById() {
// Given
User user = new User();
user.setName("John Doe");
userRepository.save(user);
// When
Optional<User> foundUser = userRepository.findById(user.getId());
// Then
assertTrue(foundUser.isPresent());
assertEquals("John Doe", foundUser.get().getName());
}
}
- Using @SpringBootTest
We can use @SpringBootTest as in the example above. In this case, the entire application context is loaded, including all the beans, and can be used to test repository beans with a real or in-memory database.
- Using Testcontainers -> Great for realistic integration tests with real databases running in Docker containers.
#
Unit & Integration tests with Maven
Unit tests
- to run :
mvn test
(the "test" phase) - run by Surefire Plugin
- run from src/test/java
Integration tests
- to run :
mvn verify
(the "verify" phase) - run by Failsafe Plugin
- run from src/integration-test/java or src/test/java with a name using the Failsafe convention (*IT.java).
Info
mvn verify
runs the unit tests alsomvn verify -DskipTests
runs the integration tests only
#
@Mock vs @InjectMocks
@Mock
// Mock dependency
private ShippingService shippingService;
@InjectMocks
// Class under test with dependencies injected
private OrderService orderService;