Spring Validator类单元测试依赖未初始化问题(已部分解决)
When dealing with Spring ConstraintValidator implementations that have autowired dependencies (like your UniqueEmailValidator with AccountService), the key is to mock the dependency and properly initialize the validator in your unit tests. Here's a step-by-step approach using Mockito for isolation and fast test execution:
1. Test Class Setup
First, set up your test class with Mockito extensions to create mocks and inject them into the validator:
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import javax.validation.ConstraintValidatorContext; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UniqueEmailValidatorTest { @Mock private AccountService accountService; @Mock private ConstraintValidatorContext constraintValidatorContext; private UniqueEmailValidator validator; @BeforeEach void setUp() { // Initialize the validator instance validator = new UniqueEmailValidator(); // Inject the mocked AccountService into the validator's autowired field ReflectionTestUtils.setField(validator, "accountService", accountService); // Call initialize with a mock of your UniqueEmail annotation (matches Spring's initialization flow) validator.initialize(mock(UniqueEmail.class)); } }
Why This Works:
@Mockcreates a lightweight mock ofAccountService, so we don't need a real database or service implementation for testing.ReflectionTestUtilshandles injecting the mock into the validator's privateaccountServicefield—since the validator uses a no-args constructor, direct injection isn't possible without Spring's context.- Calling
initialize()explicitly mirrors how Spring sets up validators in production, even if the method is empty in your implementation.
2. Core Test Scenarios
Write test cases to cover the main validation logic:
Case 1: Email is Unique (Valid)
@Test void isValid_WhenEmailDoesNotExist_ReturnsTrue() { // Arrange String uniqueEmail = "user@example.com"; when(accountService.existsByEmail(uniqueEmail)).thenReturn(false); // Act boolean result = validator.isValid(uniqueEmail, constraintValidatorContext); // Assert assertTrue(result); verify(accountService, times(1)).existsByEmail(uniqueEmail); }
Case 2: Email Already Exists (Invalid)
@Test void isValid_WhenEmailExists_ReturnsFalse() { // Arrange String existingEmail = "existing@user.com"; when(accountService.existsByEmail(existingEmail)).thenReturn(true); // Act boolean result = validator.isValid(existingEmail, constraintValidatorContext); // Assert assertFalse(result); verify(accountService, times(1)).existsByEmail(existingEmail); }
Case 3: Null Email (Adjust Based on Your Rules)
If your validation allows null emails (e.g., the field is optional), add this test to ensure no unnecessary service calls are made:
@Test void isValid_WhenEmailIsNull_ReturnsTrue() { // Act boolean result = validator.isValid(null, constraintValidatorContext); // Assert assertTrue(result); verify(accountService, never()).existsByEmail(any()); }
Alternative: Using @InjectMocks
Instead of ReflectionTestUtils, you can use Mockito's @InjectMocks to automatically inject the mock AccountService into the validator:
@ExtendWith(MockitoExtension.class) class UniqueEmailValidatorTest { @Mock private AccountService accountService; @Mock private ConstraintValidatorContext constraintValidatorContext; @InjectMocks private UniqueEmailValidator validator; @BeforeEach void setUp() { validator.initialize(mock(UniqueEmail.class)); } // ... test cases remain identical to above }
This works seamlessly if the validator's accountService field is annotated with @Autowired—Mockito handles the injection automatically.
Key Notes
- Skip using
@SpringBootTestfor this unit test: it starts the entire Spring context, making tests slower and less focused on the validator itself. Mockito gives you isolated, fast tests. - Always verify interactions with the mocked dependency (like checking
accountService.existsByEmail()was called) to ensure your validation logic is interacting with the service correctly.
内容的提问来源于stack exchange,提问作者user1778855

