Java Testing Frameworks¶
This guide covers Java testing frameworks, best practices, and strategies for writing effective tests.
🎯 Testing Fundamentals¶
Testing Pyramid¶
- Unit Tests: Fast, isolated tests for individual components
- Integration Tests: Test component interactions
- End-to-End Tests: Test complete user workflows
- Performance Tests: Load and stress testing
Testing Principles¶
- AAA Pattern: Arrange, Act, Assert
- First Principles: Fast, Independent, Repeatable, Self-validating, Timely
- Test Coverage: Aim for high coverage of critical paths
- Test Naming: Descriptive test names that explain behavior
🧪 JUnit Framework¶
JUnit 5 (Jupiter)¶
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
@DisplayName("Should add two positive numbers")
void shouldAddTwoPositiveNumbers() {
// Arrange
int a = 5;
int b = 3;
// Act
int result = calculator.add(a, b);
// Assert
assertEquals(8, result, "5 + 3 should equal 8");
}
@Test
@DisplayName("Should throw exception for division by zero")
void shouldThrowExceptionForDivisionByZero() {
// Arrange
int a = 10;
int b = 0;
// Act & Assert
assertThrows(ArithmeticException.class, () -> calculator.divide(a, b));
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
@DisplayName("Should handle positive numbers")
void shouldHandlePositiveNumbers(int number) {
assertTrue(calculator.isPositive(number));
}
@Test
@Disabled("Feature not yet implemented")
@DisplayName("Should calculate square root (disabled)")
void shouldCalculateSquareRoot() {
// Test for future feature
}
}
JUnit 5 Advanced Features¶
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;
class AdvancedCalculatorTest {
@TestFactory
Stream<DynamicTest> dynamicTestsForPalindrome() {
PalindromeChecker checker = new PalindromeChecker();
return Stream.of("racecar", "level", "radar")
.map(word -> DynamicTest.dynamicTest(
"Test palindrome: " + word,
() -> assertTrue(checker.isPalindrome(word))
));
}
@ParameterizedTest
@CsvSource({
"2, 3, 6",
"0, 5, 0",
"-2, 3, -6"
})
@DisplayName("Should multiply numbers")
void shouldMultiplyNumbers(int a, int b, int expected) {
Calculator calculator = new Calculator();
assertEquals(expected, calculator.multiply(a, b));
}
@RepeatedTest(5)
@DisplayName("Should handle random operations")
void shouldHandleRandomOperations(RepetitionInfo repetitionInfo) {
Calculator calculator = new Calculator();
int random1 = (int) (Math.random() * 100);
int random2 = (int) (Math.random() * 100);
int result = calculator.add(random1, random2);
assertEquals(random1 + random2, result);
}
}
🎭 Mockito Framework¶
Mockito Basics¶
import org.junit.jupiter.api.Test;
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService;
@Test
void shouldCreateUserAndSendEmail() {
// Arrange
User user = new User("[email protected]", "password");
when(userRepository.save(any(User.class))).thenReturn(user);
doNothing().when(emailService).sendWelcomeEmail(user.getEmail());
// Act
User createdUser = userService.createUser(user.getEmail(), user.getPassword());
// Assert
assertNotNull(createdUser);
assertEquals("[email protected]", createdUser.getEmail());
// Verify interactions
verify(userRepository).save(user);
verify(emailService).sendWelcomeEmail(user.getEmail());
}
@Test
void shouldThrowExceptionWhenEmailAlreadyExists() {
// Arrange
String email = "[email protected]";
when(userRepository.existsByEmail(email)).thenReturn(true);
// Act & Assert
assertThrows(UserAlreadyExistsException.class,
() -> userService.createUser(email, "password"));
// Verify no save or email sent
verify(userRepository, never()).save(any(User.class));
verify(emailService, never()).sendWelcomeEmail(anyString());
}
}
Mockito Advanced¶
import org.junit.jupiter.api.Test;
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
import static org.junit.jupiter.api.Assertions.*;
class AdvancedUserServiceTest {
@Mock
private UserRepository userRepository;
@Captor
private ArgumentCaptor<User> userCaptor;
@Test
void shouldCreateUserWithCorrectData() {
// Arrange
UserService userService = new UserService(userRepository);
String email = "[email protected]";
String password = "password123";
// Act
userService.createUser(email, password);
// Assert
verify(userRepository).save(userCaptor.capture());
User savedUser = userCaptor.getValue();
assertEquals(email, savedUser.getEmail());
assertNotNull(savedUser.getPasswordHash());
assertNotEquals(password, savedUser.getPasswordHash()); // Should be hashed
}
@Test
void shouldHandleRepositoryException() {
// Arrange
when(userRepository.save(any(User.class)))
.thenThrow(new DatabaseException("Connection failed"));
UserService userService = new UserService(userRepository);
// Act & Assert
assertThrows(UserCreationException.class,
() -> userService.createUser("[email protected]", "password"));
}
}
🌐 Spring Testing¶
Spring Boot Testing¶
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private EmailService emailService;
@Test
void shouldCreateUser() throws Exception {
String userJson = "{\"email\":\"[email protected]\",\"password\":\"password\"}";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("[email protected]"));
verify(emailService).sendWelcomeEmail("[email protected]");
}
}
Data JPA Testing¶
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindUserByEmail() {
// Arrange
User user = new User("[email protected]", "hashedPassword");
entityManager.persistAndFlush(user);
// Act
Optional<User> found = userRepository.findByEmail("[email protected]");
// Assert
assertTrue(found.isPresent());
assertEquals("[email protected]", found.get().getEmail());
}
@Test
void shouldReturnEmptyWhenUserNotFound() {
// Act
Optional<User> found = userRepository.findByEmail("[email protected]");
// Assert
assertFalse(found.isPresent());
}
}
🚀 Testcontainers¶
Database Testing with Testcontainers¶
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase;
@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateAndFindUser() {
// Arrange
User user = new User("[email protected]", "hashedPassword");
// Act
User saved = userRepository.save(user);
Optional<User> found = userRepository.findByEmail("[email protected]");
// Assert
assertNotNull(saved.getId());
assertTrue(found.isPresent());
assertEquals("[email protected]", found.get().getEmail());
}
}
📊 Performance Testing¶
JMH for Microbenchmarks¶
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class StringConcatenationBenchmark {
private String[] strings;
@Setup
public void setUp() {
strings = new String[1000];
for (int i = 0; i < strings.length; i++) {
strings[i] = "string" + i;
}
}
@Benchmark
public String concatenateWithPlus() {
String result = "";
for (String s : strings) {
result += s;
}
return result;
}
@Benchmark
public String concatenateWithStringBuilder() {
StringBuilder sb = new StringBuilder();
for (String s : strings) {
sb.append(s);
}
return sb.toString();
}
@Benchmark
public String concatenateWithStringJoin() {
return String.join("", strings);
}
}
🛠️ Testing Best Practices¶
Test Structure¶
class WellStructuredTest {
// 1. Clear test name
@Test
@DisplayName("Should calculate discount for premium customer")
void shouldCalculateDiscountForPremiumCustomer() {
// 2. Arrange - set up test data
Customer customer = new Customer();
customer.setPremium(true);
Order order = new Order(100.0);
// 3. Act - execute the method under test
DiscountCalculator calculator = new DiscountCalculator();
double discount = calculator.calculateDiscount(customer, order);
// 4. Assert - verify the result
assertEquals(20.0, discount, "Premium customer should get 20% discount");
}
}
Test Data Management¶
// Use builders for test data
public class UserBuilder {
private String email = "[email protected]";
private String password = "password";
private boolean active = true;
public static UserBuilder aUser() {
return new UserBuilder();
}
public UserBuilder withEmail(String email) {
this.email = email;
return this;
}
public UserBuilder withPassword(String password) {
this.password = password;
return this;
}
public User build() {
return new User(email, password, active);
}
}
// Usage in tests
@Test
void shouldCreateActiveUser() {
User user = UserBuilder.aUser()
.withEmail("[email protected]")
.build();
assertTrue(user.isActive());
}
📚 Related Resources¶
- Java Best Practices - Write testable code
- Java Common Mistakes - Avoid testing pitfalls
- Java Performance Tips - Performance testing
- Java Resources - Testing tools and documentation
🔗 Related Testing Guides¶
- Testing Strategies - General testing approaches
- Code Review Checklist - Review test quality
- Self-Assessment Techniques - Evaluate testing skills
🔗 Language-Specific Testing¶
- Python Testing Frameworks - Python testing
- C Testing Methods - C testing
- Oracle Testing Methods - Oracle testing