google-site-verification: google61fe8ba583a51912.html
Java Testing with JUnit

JUnit is the most popular testing framework for Java applications. It provides annotations and assertions to write and run repeatable tests.

1. Setup and Dependencies

Maven Dependencies

<!-- pom.xml --> <dependencies> <!-- JUnit 5 (Jupiter) --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> <!-- AssertJ for fluent assertions --> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> <scope>test</scope> </dependency> <!-- Mockito for mocking --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.5.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>5.5.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.0</version> </plugin> </plugins> </build>

Gradle Dependencies

// build.gradle dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation 'org.mockito:mockito-core:5.5.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.5.0' } test { useJUnitPlatform() }

2. Basic JUnit Testing

Simple Test Class

import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; // Test class naming convention: ClassNameTest class CalculatorTest { private Calculator calculator; // Setup method - runs before each test @BeforeEach void setUp() { calculator = new Calculator(); System.out.println("Setting up before test"); } // Cleanup method - runs after each test @AfterEach void tearDown() { calculator = null; System.out.println("Cleaning up after test"); } // Test method @Test void testAddition() { // Given int a = 5; int b = 3; // When int result = calculator.add(a, b); // Then assertEquals(8, result, "5 + 3 should equal 8"); } @Test void testSubtraction() { assertEquals(2, calculator.subtract(5, 3)); assertEquals(-2, calculator.subtract(3, 5)); } @Test void testMultiplication() { assertEquals(15, calculator.multiply(5, 3)); assertEquals(0, calculator.multiply(5, 0)); } @Test void testDivision() { assertEquals(2, calculator.divide(6, 3)); assertEquals(2.5, calculator.divide(5, 2), 0.001); } @Test void testDivisionByZero() { // Test that exception is thrown Exception exception = assertThrows(ArithmeticException.class, () -> calculator.divide(5, 0)); assertEquals("Division by zero is not allowed", exception.getMessage()); } // Display names for better test reports @Test @DisplayName("Addition with positive numbers") void additionWithPositiveNumbers() { assertAll("Multiple addition assertions", () -> assertEquals(10, calculator.add(7, 3)), () -> assertEquals(15, calculator.add(10, 5)), () -> assertEquals(0, calculator.add(0, 0)) ); } } // Class under test class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public double divide(int a, int b) { if (b == 0) { throw new ArithmeticException("Division by zero is not allowed"); } return (double) a / b; } }

3. Test Annotations and Lifecycle

Test Lifecycle Annotations

import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; class LifecycleTest { // Runs once before all tests in the class @BeforeAll static void setUpClass() { System.out.println("BeforeAll - Setting up test class"); } // Runs once after all tests in the class @AfterAll static void tearDownClass() { System.out.println("AfterAll - Cleaning up test class"); } // Runs before each test method @BeforeEach void setUp() { System.out.println("BeforeEach - Setting up test"); } // Runs after each test method @AfterEach void tearDown() { System.out.println("AfterEach - Cleaning up test"); } @Test void testOne() { System.out.println("Running testOne"); assertTrue(true); } @Test void testTwo() { System.out.println("Running testTwo"); assertFalse(false); } // Disabled test @Test @Disabled("This test is not implemented yet") void testDisabled() { fail("This test should be disabled"); } // Test with timeout @Test @Timeout(5) // 5 seconds void testWithTimeout() throws InterruptedException { Thread.sleep(3000); // This should complete within 5 seconds assertTrue(true); } }

Parameterized Tests

import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.*; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; class ParameterizedCalculatorTest { private Calculator calculator = new Calculator(); // Value Source - simple values @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 5}) void testSquareWithValueSource(int number) { int expected = number * number; assertEquals(expected, calculator.square(number)); } // Method Source - complex values from a method @ParameterizedTest @MethodSource("additionProvider") void testAdditionWithMethodSource(int a, int b, int expected) { assertEquals(expected, calculator.add(a, b)); } private static Stream<Arguments> additionProvider() { return Stream.of( Arguments.of(1, 1, 2), Arguments.of(2, 3, 5), Arguments.of(-1, 1, 0), Arguments.of(0, 0, 0) ); } // CSV Source - comma-separated values @ParameterizedTest @CsvSource({ "2, 3, 6", "5, 0, 0", "-2, 4, -8", "10, 10, 100" }) void testMultiplicationWithCsvSource(int a, int b, int expected) { assertEquals(expected, calculator.multiply(a, b)); } // CSV File Source - values from CSV file @ParameterizedTest @CsvFileSource(resources = "/division_test_data.csv", numLinesToSkip = 1) void testDivisionWithCsvFile(int dividend, int divisor, double expected) { assertEquals(expected, calculator.divide(dividend, divisor), 0.001); } // Enum Source @ParameterizedTest @EnumSource(Operation.class) void testEnumOperations(Operation operation) { assertNotNull(operation); assertTrue(operation.name().length() > 0); } } // Add this method to Calculator class class Calculator { // ... previous methods ... public int square(int number) { return number * number; } } enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE }

Test Data File

# src/test/resources/division_test_data.csv dividend,divisor,expected 10,2,5.0 15,3,5.0 7,2,3.5 100,10,10.0

4. Advanced Assertions

Using AssertJ for Fluent Assertions

import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.*; class AssertJTest { @Test void testStringAssertions() { String name = "John Doe"; assertThat(name) .isNotNull() .isNotEmpty() .hasSize(8) .contains("John") .doesNotContain("Jane") .startsWith("John") .endsWith("Doe") .isEqualTo("John Doe") .isEqualToIgnoringCase("john doe"); } @Test void testListAssertions() { List<String> fruits = Arrays.asList("apple", "banana", "cherry"); assertThat(fruits) .isNotNull() .hasSize(3) .contains("banana") .doesNotContain("orange") .containsExactly("apple", "banana", "cherry") .containsExactlyInAnyOrder("cherry", "apple", "banana") .allMatch(fruit -> fruit.length() > 3) .noneMatch(fruit -> fruit.isEmpty()); } @Test void testMapAssertions() { Map<String, Integer> scores = Map.of( "John", 85, "Jane", 92, "Bob", 78 ); assertThat(scores) .hasSize(3) .containsKeys("John", "Jane") .doesNotContainKeys("Alice") .containsEntry("John", 85) .allSatisfy((name, score) -> { assertThat(name).isNotBlank(); assertThat(score).isBetween(0, 100); }); } @Test void testExceptionAssertions() { Calculator calculator = new Calculator(); assertThatThrownBy(() -> calculator.divide(10, 0)) .isInstanceOf(ArithmeticException.class) .hasMessage("Division by zero is not allowed") .hasNoCause(); assertThatExceptionOfType(ArithmeticException.class) .isThrownBy(() -> calculator.divide(5, 0)) .withMessage("Division by zero is not allowed"); } @Test void testOptionalAssertions() { Optional<String> present = Optional.of("value"); Optional<String> empty = Optional.empty(); assertThat(present) .isPresent() .contains("value") .hasValue("value"); assertThat(empty).isEmpty(); } @Test void testObjectAssertions() { Person person = new Person("John", 30, "[email protected]"); assertThat(person) .isNotNull() .hasFieldOrProperty("name") .hasFieldOrPropertyWithValue("age", 30) .extracting(Person::getName, Person::getEmail) .containsExactly("John", "[email protected]"); } } class Person { private String name; private int age; private String email; public Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } // Getters public String getName() { return name; } public int getAge() { return age; } public String getEmail() { return email; } }

Hamcrest Matchers (Alternative)

import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; class HamcrestTest { @Test void testHamcrestMatchers() { String text = "Hello World"; List<String> items = Arrays.asList("one", "two", "three"); assertThat(text, is(notNullValue())); assertThat(text, equalTo("Hello World")); assertThat(text, containsString("World")); assertThat(text, startsWith("Hello")); assertThat(text, endsWith("World")); assertThat(items, hasSize(3)); assertThat(items, hasItem("two")); assertThat(items, contains("one", "two", "three")); assertThat(items, everyItem(hasLength(greaterThan(2)))); assertThat(15.5, closeTo(15.0, 0.6)); assertThat(10, greaterThan(5)); assertThat(10, lessThanOrEqualTo(10)); } }

5. Mocking with Mockito

Basic Mocking

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 java.util.Arrays; import java.util.List; import java.util.Optional; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; // Enable Mockito annotations @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @Mock private EmailService emailService; @InjectMocks private UserService userService; @Test void testGetUserById() { // Given User expectedUser = new User(1L, "[email protected]", "John Doe"); when(userRepository.findById(1L)).thenReturn(Optional.of(expectedUser)); // When Optional<User> result = userService.getUserById(1L); // Then assertThat(result).isPresent(); assertThat(result.get().getName()).isEqualTo("John Doe"); verify(userRepository).findById(1L); } @Test void testGetUserByIdNotFound() { // Given when(userRepository.findById(999L)).thenReturn(Optional.empty()); // When Optional<User> result = userService.getUserById(999L); // Then assertThat(result).isEmpty(); verify(userRepository).findById(999L); } @Test void testCreateUser() { // Given User newUser = new User(null, "[email protected]", "Jane Smith"); User savedUser = new User(1L, "[email protected]", "Jane Smith"); when(userRepository.save(newUser)).thenReturn(savedUser); doNothing().when(emailService).sendWelcomeEmail(anyString()); // When User result = userService.createUser(newUser); // Then assertThat(result.getId()).isEqualTo(1L); assertThat(result.getName()).isEqualTo("Jane Smith"); verify(userRepository).save(newUser); verify(emailService).sendWelcomeEmail("[email protected]"); } @Test void testGetAllUsers() { // Given List<User> users = Arrays.asList( new User(1L, "[email protected]", "John Doe"), new User(2L, "[email protected]", "Jane Smith") ); when(userRepository.findAll()).thenReturn(users); // When List<User> result = userService.getAllUsers(); // Then assertThat(result).hasSize(2); assertThat(result).extracting(User::getName) .containsExactly("John Doe", "Jane Smith"); verify(userRepository).findAll(); } @Test void testDeleteUser() { // Given Long userId = 1L; when(userRepository.existsById(userId)).thenReturn(true); doNothing().when(userRepository).deleteById(userId); // When boolean result = userService.deleteUser(userId); // Then assertThat(result).isTrue(); verify(userRepository).existsById(userId); verify(userRepository).deleteById(userId); } @Test void testDeleteUserNotFound() { // Given Long userId = 999L; when(userRepository.existsById(userId)).thenReturn(false); // When boolean result = userService.deleteUser(userId); // Then assertThat(result).isFalse(); verify(userRepository).existsById(userId); verify(userRepository, never()).deleteById(anyLong()); } @Test void testArgumentMatchers() { User user = new User(1L, "[email protected]", "Test User"); when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(user)); when(userRepository.findByEmail(startsWith("admin"))).thenReturn(Optional.empty()); Optional<User> result1 = userService.findByEmail("[email protected]"); Optional<User> result2 = userService.findByEmail("[email protected]"); assertThat(result1).isPresent(); assertThat(result2).isEmpty(); verify(userRepository, times(2)).findByEmail(anyString()); } } // Service classes under test class UserService { private final UserRepository userRepository; private final EmailService emailService; public UserService(UserRepository userRepository, EmailService emailService) { this.userRepository = userRepository; this.emailService = emailService; } public Optional<User> getUserById(Long id) { return userRepository.findById(id); } public User createUser(User user) { User savedUser = userRepository.save(user); emailService.sendWelcomeEmail(savedUser.getEmail()); return savedUser; } public List<User> getAllUsers() { return userRepository.findAll(); } public boolean deleteUser(Long id) { if (userRepository.existsById(id)) { userRepository.deleteById(id); return true; } return false; } public Optional<User> findByEmail(String email) { return userRepository.findByEmail(email); } } interface UserRepository { Optional<User> findById(Long id); User save(User user); List<User> findAll(); boolean existsById(Long id); void deleteById(Long id); Optional<User> findByEmail(String email); } interface EmailService { void sendWelcomeEmail(String email); } class User { private Long id; private String email; private String name; public User(Long id, String email, String name) { this.id = id; this.email = email; this.name = name; } // Getters public Long getId() { return id; } public String getEmail() { return email; } public String getName() { return name; } }

Advanced Mocking

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 java.time.LocalDateTime; import java.util.List; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class OrderServiceTest { @Mock private OrderRepository orderRepository; @Mock private PaymentService paymentService; @Mock private InventoryService inventoryService; @InjectMocks private OrderService orderService; @Test void testProcessOrderSuccess() { // Given Order order = new Order(1L, 100.0, "PENDING"); when(inventoryService.checkAvailability(any(Order.class))).thenReturn(true); when(paymentService.processPayment(any(Order.class))).thenReturn(true); when(orderRepository.save(any(Order.class))).thenReturn(order); // When boolean result = orderService.processOrder(order); // Then assertThat(result).isTrue(); assertThat(order.getStatus()).isEqualTo("COMPLETED"); verify(inventoryService).checkAvailability(order); verify(paymentService).processPayment(order); verify(orderRepository).save(order); } @Test void testProcessOrderInventoryUnavailable() { // Given Order order = new Order(1L, 100.0, "PENDING"); when(inventoryService.checkAvailability(any(Order.class))).thenReturn(false); // When boolean result = orderService.processOrder(order); // Then assertThat(result).isFalse(); assertThat(order.getStatus()).isEqualTo("CANCELLED"); verify(inventoryService).checkAvailability(order); verify(paymentService, never()).processPayment(any(Order.class)); verify(orderRepository).save(order); } @Test void testProcessOrderPaymentFailed() { // Given Order order = new Order(1L, 100.0, "PENDING"); when(inventoryService.checkAvailability(any(Order.class))).thenReturn(true); when(paymentService.processPayment(any(Order.class))).thenReturn(false); // When boolean result = orderService.processOrder(order); // Then assertThat(result).isFalse(); assertThat(order.getStatus()).isEqualTo("FAILED"); verify(inventoryService).checkAvailability(order); verify(paymentService).processPayment(order); verify(orderRepository).save(order); } @Test void testVerifyInteractionOrder() { Order order = new Order(1L, 100.0, "PENDING"); when(inventoryService.checkAvailability(any(Order.class))).thenReturn(true); when(paymentService.processPayment(any(Order.class))).thenReturn(true); when(orderRepository.save(any(Order.class))).thenReturn(order); orderService.processOrder(order); // Verify order of interactions inOrder(inventoryService, paymentService, orderRepository).verify(inventoryService).checkAvailability(order); inOrder(inventoryService, paymentService, orderRepository).verify(paymentService).processPayment(order); inOrder(inventoryService, paymentService, orderRepository).verify(orderRepository).save(order); } @Test void testSpyExample() { List<String> list = Arrays.asList("one", "two", "three"); List<String> spyList = spy(list); // You can stub specific methods when(spyList.size()).thenReturn(100); // But other methods work as normal assertThat(spyList.get(0)).isEqualTo("one"); assertThat(spyList.size()).isEqualTo(100); verify(spyList).get(0); verify(spyList).size(); } } // Supporting classes class OrderService { private final OrderRepository orderRepository; private final PaymentService paymentService; private final InventoryService inventoryService; public OrderService(OrderRepository orderRepository, PaymentService paymentService, InventoryService inventoryService) { this.orderRepository = orderRepository; this.paymentService = paymentService; this.inventoryService = inventoryService; } public boolean processOrder(Order order) { if (!inventoryService.checkAvailability(order)) { order.setStatus("CANCELLED"); orderRepository.save(order); return false; } if (!paymentService.processPayment(order)) { order.setStatus("FAILED"); orderRepository.save(order); return false; } order.setStatus("COMPLETED"); orderRepository.save(order); return true; } } interface OrderRepository { Order save(Order order); } interface PaymentService { boolean processPayment(Order order); } interface InventoryService { boolean checkAvailability(Order order); } class Order { private Long id; private Double amount; private String status; private LocalDateTime createdAt; public Order(Long id, Double amount, String status) { this.id = id; this.amount = amount; this.status = status; this.createdAt = LocalDateTime.now(); } // Getters and setters public Long getId() { return id; } public Double getAmount() { return amount; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public LocalDateTime getCreatedAt() { return createdAt; } }

6. Integration Testing

Spring Boot Integration Tests

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.TestPropertySource; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; // Spring Boot test with minimal configuration @SpringBootTest @TestPropertySource(locations = "classpath:application-test.properties") class UserServiceIntegrationTest { @Autowired private UserService userService; @MockBean private UserRepository userRepository; @MockBean private EmailService emailService; @Test void contextLoads() { // Verify that Spring context loads successfully assertThat(userService).isNotNull(); } @Test void testCreateUserIntegration() { // Given User newUser = new User(null, "[email protected]", "Integration Test"); User savedUser = new User(1L, "[email protected]", "Integration Test"); when(userRepository.save(any(User.class))).thenReturn(savedUser); // When User result = userService.createUser(newUser); // Then assertThat(result).isNotNull(); assertThat(result.getId()).isEqualTo(1L); assertThat(result.getEmail()).isEqualTo("[email protected]"); } @Test void testFindUserIntegration() { // Given User user = new User(1L, "[email protected]", "Find Test"); when(userRepository.findById(1L)).thenReturn(Optional.of(user)); // When Optional<User> result = userService.getUserById(1L); // Then assertThat(result).isPresent(); assertThat(result.get().getName()).isEqualTo("Find Test"); } }

Test Configuration

# src/test/resources/application-test.properties spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true logging.level.com.example=DEBUG

7. Test Organization and Best Practices

Nested Tests for Better Organization

import org.junit.jupiter.api.*; import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.*; @DisplayName("User Management System") class UserManagementTest { private Map<Long, User> userDatabase; private UserService userService; @BeforeEach void setUp() { userDatabase = new HashMap<>(); userService = new UserService(userDatabase); } @Nested @DisplayName("When no users exist") class NoUsers { @Test @DisplayName("Should return empty list") void shouldReturnEmptyList() { assertThat(userService.getAllUsers()).isEmpty(); } @Test @DisplayName("Should not find user by ID") void shouldNotFindUserById() { assertThat(userService.getUserById(1L)).isEmpty(); } } @Nested @DisplayName("When users exist") class UsersExist { @BeforeEach void setUp() { userService.createUser(new User(null, "[email protected]", "User One")); userService.createUser(new User(null, "[email protected]", "User Two")); } @Test @DisplayName("Should return all users") void shouldReturnAllUsers() { assertThat(userService.getAllUsers()).hasSize(2); } @Test @DisplayName("Should find user by ID") void shouldFindUserById() { Optional<User> user = userService.getUserById(1L); assertThat(user).isPresent(); assertThat(user.get().getName()).isEqualTo("User One"); } @Nested @DisplayName("When updating users") class UpdateUsers { @Test @DisplayName("Should update user email") void shouldUpdateUserEmail() { User updatedUser = new User(1L, "[email protected]", "User One"); userService.updateUser(updatedUser); Optional<User> user = userService.getUserById(1L); assertThat(user).isPresent(); assertThat(user.get().getEmail()).isEqualTo("[email protected]"); } } } @Nested @DisplayName("User validation") class UserValidation { @Test @DisplayName("Should reject null email") void shouldRejectNullEmail() { User invalidUser = new User(null, null, "Name"); assertThatThrownBy(() -> userService.createUser(invalidUser)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Email cannot be null or empty"); } @Test @DisplayName("Should reject empty name") void shouldRejectEmptyName() { User invalidUser = new User(null, "[email protected]", ""); assertThatThrownBy(() -> userService.createUser(invalidUser)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Name cannot be null or empty"); } } } // Enhanced UserService for testing class UserService { private final Map<Long, User> userDatabase; private Long nextId = 1L; public UserService(Map<Long, User> userDatabase) { this.userDatabase = userDatabase; } public User createUser(User user) { validateUser(user); user.setId(nextId++); userDatabase.put(user.getId(), user); return user; } public Optional<User> getUserById(Long id) { return Optional.ofNullable(userDatabase.get(id)); } public void updateUser(User user) { if (!userDatabase.containsKey(user.getId())) { throw new IllegalArgumentException("User not found with id: " + user.getId()); } validateUser(user); userDatabase.put(user.getId(), user); } public java.util.List<User> getAllUsers() { return new java.util.ArrayList<>(userDatabase.values()); } private void validateUser(User user) { if (user.getEmail() == null || user.getEmail().trim().isEmpty()) { throw new IllegalArgumentException("Email cannot be null or empty"); } if (user.getName() == null || user.getName().trim().isEmpty()) { throw new IllegalArgumentException("Name cannot be null or empty"); } } }

Test Templates and Repeated Tests

import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.*; @ExtendWith(MockitoExtension.class) class AdvancedTestExamples { @RepeatedTest(5) @DisplayName("Repeated test example") void repeatedTest(RepetitionInfo repetitionInfo) { System.out.println("Repetition " + repetitionInfo.getCurrentRepetition() + " of " + repetitionInfo.getTotalRepetitions()); assertThat(repetitionInfo.getCurrentRepetition()).isPositive(); } @TestFactory Stream<DynamicTest> dynamicTests() { return Stream.of( DynamicTest.dynamicTest("First dynamic test", () -> { assertThat(2 + 2).isEqualTo(4); }), DynamicTest.dynamicTest("Second dynamic test", () -> { assertThat("hello".toUpperCase()).isEqualTo("HELLO"); }), DynamicTest.dynamicTest("Third dynamic test", () -> { assertThat(true).isTrue(); }) ); } @ParameterizedTest @MethodSource("provideTestData") void testWithMultipleParameters(int input, int expected, String description) { Calculator calculator = new Calculator(); assertThat(calculator.square(input)).isEqualTo(expected); } private static Stream<Arguments> provideTestData() { return Stream.of( Arguments.of(1, 1, "Square of 1"), Arguments.of(2, 4, "Square of 2"), Arguments.of(3, 9, "Square of 3"), Arguments.of(4, 16, "Square of 4"), Arguments.of(5, 25, "Square of 5") ); } }

8. Best Practices Summary

Testing Best Practices

/** * TESTING BEST PRACTICES: * * 1. Test Structure: Use Given-When-Then pattern * 2. Naming: Use descriptive test names (@DisplayName) * 3. Single Responsibility: One assertion per test concept * 4. Independence: Tests should not depend on each other * 5. Fast: Tests should run quickly * 6. Deterministic: Same input should always produce same output * 7. Meaningful Assertions: Use descriptive assertion messages * 8. Mock Wisely: Only mock external dependencies * 9. Coverage: Test both happy path and edge cases * 10. Organization: Use nested tests for complex scenarios */ public class TestingBestPractices { /* GOOD TEST CHARACTERISTICS: - Fast - Isolated - Repeatable - Self-validating - Timely - Readable - Maintainable */ }

Summary

Key JUnit 5 Features:

  • Annotations: @Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll
  • Assertions: assertEquals(), assertTrue(), assertThrows(), etc.
  • Parameterized Tests: @ParameterizedTest with various sources
  • Mocking: Mockito integration with @Mock and @InjectMocks
  • Advanced Testing: Nested tests, dynamic tests, repeated tests

Testing Pyramid:

  • Unit Tests: Fast, isolated tests of individual components
  • Integration Tests: Test interactions between components
  • End-to-End Tests: Test complete system workflows

Tools and Libraries:

  • JUnit 5: Core testing framework
  • AssertJ: Fluent assertions
  • Mockito: Mocking framework
  • Spring Boot Test: Integration testing support

Effective testing leads to more reliable, maintainable, and bug-free code. Always aim for good test coverage and follow testing best practices.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper