简体   繁体   中英

how to test method that starts new thread which may fail due to operating system

I have an application built upon Spring Boot. There is simple controller with a method which creates new Thread and starts it. However a runnable executes unix command (nc) (used ProcessBuilder for that). Thus when I'm runnning it on the windows I get exceptions from started thread. Indeed it can not run unix program. Now I would like to write a test for this controller, but I'm wondering is it possible and reasonable. I was thinking about changing behaviour of runnable task just for testing, although I don't know how can it be done. Thanks for any help and other ideas/solutions for this case.

Controller:

@Controller
public class TaskController {

    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(task-%d").build();

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {
        Runnable runnable= new Task();       
        threadFactory.newThread(runnable).start();  
        return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
}

Task:

public class Task implements Runnable {

    @Override
    public void run() {
        // start unix process
    }
}

Application class:

@ComponentScan
@EnableAutoConfiguration
public class Application {

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

Integration Test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class ApplicationTest {

@Value("${local.server.port}")
private int port;

@Test
public void shouldStartTask() throws Exception {
    // when
    ResponseEntity<String> entity = new TestRestTemplate().getForEntity("http://localhost:" + this.port + "/startTask", String.class);

    // then
    assertThat(entity.getStatusCode()).isSameAs(HttpStatus.ACCEPTED);
}

}

You might find it easier to test your program if you the extract the processing logic of your application (which does things using threads) from your controller logic, placing the processing logic in a separate service layer , which your controller delegates to. Design the service layer to have an API that is easy to unit test, by providing methods for accessing its current state, not just for performing actions. Use dependency injection to connect your controller to your service layer.

So, something like this:

 public interface Service
 {

      // Sets this.wasTaskStarted() == true
      void startTask();

      boolean wasTaskStarted();

      void awaitCompletionOfTask();
 }

 @Controller
 public class TaskController {
    private final Service service;

    @Autowired
    public TaskController(Service service) {
       this.service = service;
    }

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {
       service.startTask();
       return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
 }

 public ServiceImpl implements Service {
      private final ThreadFactor threadFactory = new ....;
      private Thread taskTread;

      @Override
      public synchronized void startTask() {
         if (taskTread == null) {
            taskTread = threadFactory.newThread(new Task());
            taskTread.start();
            notifyAll();
         }
         // else already started
      }

      @Override
      public synchronized boolean wasTaskStarted() {
         return taskTread != null;
      }

      @Override
      public synchronized void awaitCompletionOfTask() {
         while (taskTread == null) {
            wait();
         }
         taskTread.join();
      }
 }

To test that your controller starts a task, you just need to test that Service.wasTaskStarted() is true after calling TaskController.startTask() .

You also have to test your service layer:

 public class ServiceImplTest
 {

    @Test
    public void testStartTask() {
       final ServiceImpl service = new ServiceImpl(....);
       service.startTask();
       assert(service.wasTastStarted());
    }
    @Test
    public void testRunTask() {
       final ServiceImpl service = new ServiceImpl(....);
       service.startTask();
       service.awaitCompletionOfTask();
       // Add assertions here to test that the task did what it ought to do
    }
 }

Thanks for the suggestion. You just opened my mind and I changed the design a bit. I resigned from an integration test. From business point of view, I don't need to check whether task has been started or even completed. Now it looks as follows:

Controller:

@Controller
public class TaskController {

    private ThreadService threadService;

    @Autowired
     public TaskController (ThreadService threadService) {
        this.threadService= threadService;        
    }

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {    
        // some conditions here which I would like to test 
        threadService.startNewThread(new Task());
        return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
}

Task:

public class Task implements Runnable {

    @Override
    public void run() {
        // start unix process
    }
}

Thread service:

@Component
public class ThreadService {

    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("task-%d").build();

    public void startNewThread(Runnnable task) {
        threadFactory.newThread(task).start();
    }

}

And I decided to unit test my controller, stubbing ThreadService with mockito:

@RunWith(MockitoJUnitRunner.class)
public class TaskControllerTest {

    @Mock
    ThreadService threadService;

    @InjectMocks
    private TaskController objectUnderTest;

    @Test
    public void shouldStartTask() throws FileNotFoundException {
        // when
        ResponseEntity<String> response = objectUnderTest.startTask();

        // then
        assertThat(response.getStatusCode()).isSameAs(HttpStatus.ACCEPTED);
        // more assertions
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM