【问题标题】:Can not execute controller test with @SpringBootTest无法使用@SpringBootTest 执行控制器测试
【发布时间】:2020-08-27 12:02:41
【问题描述】:

我有一个 Spring Boot 应用程序。版本是2.3.1

主应用程序如下所示:

@AllArgsConstructor
@SpringBootApplication
public class LocalServiceApplication implements CommandLineRunner {
    private final DataService dataService;
    private final QrReaderServer qrReaderServer;
    private final MonitoringService monitoringService;

    @Override
    public void run(String... args) {
        dataService.fetchData();
        monitoringService.launchMonitoring();
        qrReaderServer.launchServer();
    }

    public static void main(String[] args) {
        SpringApplication.run(LocalServiceApplication.class, args);
    }
}

应用程序启动后,我必须执行与CommandLineRunner 完成的 3 个不同步骤:

  • 首先获取远程数据并将其存储在本地(对于测试配置文件,此步骤被跳过)
  • 使用WatchService 启动文件上传的异步文件夹监控。
  • 启动 TCP 服务器

我有一个类似的控制器:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/v1/permissions")
public class CarParkController {
    private final PermissionService permissionService;

    @PostMapping
    public CarParkPermission createPermission(@RequestBody @Valid CarParkPermission permission) {
        return permissionService.createPermission(permission);
    }
}

使用 Junit 5 进行的 Ant 测试如下所示:

@ActiveProfiles("test")
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class CarParkControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private PermissionService permissionService;
    private final Gson gson = new Gson();

    @Test
    void testCreatingNewPermissionSuccess() throws Exception {
        CarParkPermission permission = CarParkPermission.builder()
                .id(56)
                .permissionCode("1234")
                .build();

        when(permissionService.createPermission(refEq(permission))).thenReturn(permission);

        postPermission(permission).andExpect(status().isOk());
    }

    private <T> ResultActions postPermission(T instance) throws Exception {
        return this.mockMvc.perform(post("/v1/permissions")
                .contentType(MediaType.APPLICATION_JSON)
                .content(gson.toJson(instance)));
    }
}

看起来应该可以正常工作。

但是,测试没有执行:

2020-08-27 14:42:30.308  INFO 21800 --- [           main] c.s.i.CarParkControllerIntegrationTest   : Started CarParkControllerIntegrationTest in 8.593 seconds (JVM running for 10.03)
2020-08-27 14:42:30.334  INFO 21800 --- [           main] c.s.s.s.DataServiceTestImpl     : Fetch data for test profile is skipped
2020-08-27 14:42:30.336 DEBUG 21800 --- [   carpark-ex-1] c.s.monitoring.MonitoringServiceImpl     : START_MONITORING Results from Cameras for folder: D:\results-from-camera
2020-08-27 14:42:30.751 DEBUG 21800 --- [           main] c.s.netty.TCPServer              : TCP Server is STARTED : port 9090

在这些行之后,执行将永远挂起。

更新

以下是监控任务的详细信息:

@Async
@Override
public void launchMonitoring() {
    log.debug("START_MONITORING Results from Cameras for folder: {}", properties.getFolder());
    try {
        WatchKey key;
        while ((key = watchService.take()) != null) {
            for (WatchEvent<?> event : key.pollEvents()) {

                WatchEvent.Kind<?> kind = event.kind();
                if (kind == ENTRY_CREATE) {
                    log.info("FILE_CREATED: {}", event.context());
                    // processing resource

                    deleteResource(zipFullPath);
                } else if (kind == ENTRY_DELETE) {
                    log.info("RESOURCE_DELETED: {}", event.context());
                }
            }
            key.reset();
        }
    } catch (InterruptedException e) {
        log.error("interrupted exception for monitoring service", e);
        Thread.currentThread().interrupt();
    } 
}

另外AsyncConfiguration 配置了TaskExecutor。

从 TCPServer 的启动方法看起来:

@Override
public void launchServer() {
    try {
        ChannelFuture serverChannelFuture = serverBootstrap.bind(hostAddress).sync();
        log.debug("TCP Server is STARTED : port {}", hostAddress.getPort());

        serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        shutdownQuietly();
    }
}

如何解决这个问题?

【问题讨论】:

  • 因为你正在启动线程监控的东西,它不会继续启动。基本上你的CommandLineRunner 永远挂起。
  • @M.Deinum 更新了test 配置文件的 2 种运行方法的问题。这种情况是否存在任何解决方案或解决方法?
  • 没有@EnableAsync 所以@Async 没有任何作用。此外,您正在启动一个阻塞的 tcp 服务器。即使使用@Async,由于通过构造函数将它们注入@SpringBootApplication 带注释的类,bean 的早期初始化也可能(并且可能不会)工作。
  • @M.Deinum EnableAsync 已启用。它工作正常。在做出这个决定之前,只执行了第一个 bean。它与 taskSchedulergetAsyncUncaughtExceptionHandler 实现完全分开配置 AsyncConfiguration
  • 服务器的启动被阻塞(如前所述),因此tomcat初始化永远不会完成,因此您的测试永远不会启动。

标签: spring spring-boot testing integration-testing startup


【解决方案1】:

了解执行被阻止(感谢M.Deinum)。

所以把最后一个方法改成:

@Async
@Override
public void launchServer() { 
    // ...
}

并转移到 ObjectMapper 而不是 Gson 以将实例转换为 JSON 格式:

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class CarParkControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper mapper;

    @Test
    void testCreatingNewPermissionSuccess() throws Exception {
        CarParkPermission permission = CarParkPermission.builder()
                .id(444)
                .permissionCode("1234")
                .build();
        postPermission(permission).andExpect(status().isOk());
    }

    private <T> ResultActions postPermission(T instance) throws Exception {
        return this.mockMvc.perform(post("/v1/permissions")
                .contentType(MediaType.APPLICATION_JSON)
                .content(mapper.writeValueAsString(instance)));
    }
}

最后,它工作正常。

【讨论】:

    猜你喜欢
    • 2021-02-20
    • 2013-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-11
    相关资源
    最近更新 更多