Rez*_*our 6 java spring spring-test mockito spring-boot
主要问题:有没有办法在 Spring 的整个上下文中用模拟对象替换 bean,并将确切的 bean 注入到测试中以验证方法调用?
我有一个 Spring Boot 应用程序,我正在尝试编写一些集成测试,其中我使用MockMvc.
Testcontainer使用和针对实际数据库和 AWS 资源运行集成测试Localstack。但为了测试作为外部依赖项集成的 API Keycloak,我决定模拟KeycloakService并验证是否将正确的参数传递给此类的正确函数。
我的所有集成测试类都是名为的抽象类的子类AbstractSpringIntegrationTest:
@Transactional
@Testcontainers
@ActiveProfiles("it")
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@ContextConfiguration(initializers = PostgresITConfig.DockerPostgreDataSourceInitializer.class, classes = {AwsITConfig.class})
public class AbstractSpringIntegrationTest {
@Autowired
public MockMvc mockMvc;
@Autowired
public AmazonSQSAsync amazonSQS;
}
Run Code Online (Sandbox Code Playgroud)
考虑有一个类似以下类的子类:
class UserIntegrationTest extends AbstractSpringIntegrationTest {
private static final String USERS_BASE_URL = "/users";
@Autowired
private UserRepository userRepository;
@MockBean
private KeycloakService keycloakService;
@ParameterizedTest
@ValueSource(booleans = {true, false})
void changeUserStatus_shouldEnableOrDisableTheUser(boolean enabled) throws Exception {
// Some test setup here
ChangeUserStatusRequest request = new ChangeUserStatusRequest()
.setEnabled(enabled);
String responseString = mockMvc.perform(patch(USERS_BASE_URL + "/{id}/status", id)
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// Some assertions here
Awaitility.await()
.atMost(10, SECONDS)
.untilAsserted(() -> verify(keycloakService, times(1)).changeUserStatus(email, enabled); // Fails to verify method call
}
}
Run Code Online (Sandbox Code Playgroud)
这是根据事件调用函数的类KeycloakService:
@Slf4j
@Component
public class UserEventSQSListener {
private final KeycloakService keycloakService;
public UserEventSQSListener(KeycloakService keycloakService) {
this.keycloakService = keycloakService;
}
@SqsListener(value = "${cloud.aws.sqs.user-status-changed-queue}", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void handleUserStatusChangedEvent(UserStatusChangedEvent event) {
keycloakService.changeUserStatus(event.getEmail(), event.isEnabled());
}
}
Run Code Online (Sandbox Code Playgroud)
每当我运行测试时,我都会收到以下错误:
Wanted but not invoked:
keycloakService bean.changeUserStatus(
"rodolfo.kautzer@example.com",
true
);
Actually, there were zero interactions with this mock.
Run Code Online (Sandbox Code Playgroud)
调试代码后,我了解到由于上下文重新加载,模拟的 beanUserIntegrationTest与注入到类中的 bean 不同。UserEventSQSListener因此,我尝试了其他解决方案,例如使用创建模拟对象Mockito.mock()并将其作为 bean 返回,以及使用@MockInBean,但它们效果不佳。
@TestConfiguration
public static class TestBeanConfig {
@Bean
@Primary
public KeycloakService keycloakService() {
KeycloakService keycloakService = Mockito.mock(KeycloakService.class);
return keycloakService;
}
}
Run Code Online (Sandbox Code Playgroud)
更新1:
根据@Maziz的回答并出于调试目的,我更改了代码,如下所示:
@Component
public class UserEventSQSListener {
private final KeycloakService keycloakService;
public UserEventSQSListener(KeycloakService keycloakService) {
this.keycloakService = keycloakService;
}
public KeycloakService getKeycloakService() {
return keycloakService;
}
...
Run Code Online (Sandbox Code Playgroud)
class UserIT extends AbstractSpringIntegrationTest {
...
@Autowired
private UserEventSQSListener userEventSQSListener;
@Autowired
private Map<String, UserEventSQSListener> beans;
private KeycloakService keycloakService;
@BeforeEach
void setup() {
...
keycloakService = mock(KeycloakService.class);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void changeUserStatus_shouldEnableOrDisableTheUser(boolean enabled) throws Exception {
// Some test setup here
ChangeUserStatusRequest request = new ChangeUserStatusRequest()
.setEnabled(enabled);
String responseString = mockMvc.perform(patch(USERS_BASE_URL + "/{id}/status", id)
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// Some assertions here
ReflectionTestUtils.setField(userEventSQSListener, "keycloakService", keycloakService);
assertThat(userEventSQSListener.getKeycloakService()).isEqualTo(keycloakService);
await().atMost(10, SECONDS)
.untilAsserted(() -> verify(keycloakService).changeUserStatus(anyString(), anyBoolean())); // Fails to verify method call
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,模拟在UserEventSQSListener类中被适当替换:
尽管如此,我还是收到了以下错误:
Wanted but not invoked:
keycloakService.changeUserStatus(
<any string>,
<any boolean>
);
Actually, there were zero interactions with this mock.
Run Code Online (Sandbox Code Playgroud)
您是否在 UserEventSQSListener 中调试了 KeyClockService?您是否看到该对象是否是代理类型,表明是模拟对象?
不管答案如何,在调用mockMvc.perform之前,可以使用
ReflectionTestUtils.setField(UserEventSQSListener, "keycloakService", keycloakService /*the mock object*/)
Run Code Online (Sandbox Code Playgroud)
再次运行。让我知道是否可以。
| 归档时间: |
|
| 查看次数: |
1785 次 |
| 最近记录: |