Search code examples
springspring-boottestingintegration-testingstartup

Can not execute controller test with @SpringBootTest


I have a Spring Boot application. Version is 2.3.1.

Main Application looks like:

@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);
    }
}

After the application is started I have to execute 3 distinct steps which have done with CommandLineRunner:

  • first gets remote data and store it locally (for test profile this step is skipped)
  • start async folder monitoring for file uploads with WatchService.
  • launch TCP server

I have a controller like:

@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);
    }
}

Ant test with Junit 5 looks like:

@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)));
    }
}

Looks like it should work fine.

However, the test isn't executed:

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

After those lines execution hangs up forever.

UPDATE

Here are details for monitoring task:

@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();
    } 
}

Also AsyncConfiguration is configured with TaskExecutor.

Launch method from TCPServer looks:

@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();
    }
}

How to solve this issue?


Solution

  • Have understood that execution is blocked (thanks to M.Deinum).

    So changed the last method for:

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

    And shifted to ObjectMapper instead of Gson for converting instance to JSON format:

    @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)));
        }
    }
    

    And finally, it works fine.