I've been playing around with Spring boot these last days, and I really like the @WebIntegrationTest annotation, that allows to startup a Tomcat and deploy a Spring boot app in it automatically. It gives the option of starting Tomcat on a random port, which is very easy if you are testing a webapp. But my case is slightly different. Whole source code is available here :
https://github.com/vincent-fuchs/spring-projects
Using Spring Batch, I need to parse an Excel file, and write the items to a REST webService. It works well when I know in advance the URL that the writer needs to use, and I am able to test the whole flow in a few seconds using Spring Boot, by loading a "hollow" @RestController that receives the requests and makes them available to the test for assertions.
(Note : I'm sure this only would be pretty useful to quite a lot of people. it would work also if you're trying to test a Spring Integration config)
It's the batch that I want to test, not the webService. But to test the output of this batch, I need this "hollow" webservice.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes =
{
//the webservice client that the batch should use (planning for SOAP impl later)
RestCustomerWsClient.class,
//the "target" web server, with the 'hollow' endpoint listening
TargetRESTSystem.class,
//the application under test and its config
TestSpecificConfiguration.class,
CustomerBatchConfiguration.class}
)
@WebIntegrationTest("server.port:8080")
@IntegrationTest({"spring.batch.job.enabled=false"})
public class CustomerBatchWithRestTest {
@Value("${local.server.port}")
public int targetWebServerPort;
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private DummyCustomerController endpoint ;
@Autowired
private ConfigurableApplicationContext appCtx ;
@Autowired
private ConfigurableEnvironment env;
@Before
public void loadDynamicProperty() {
EnvironmentTestUtils.addEnvironment(appCtx, "target.port:"+targetWebServerPort);
assertThat(env.getProperty("target.port")).isEqualTo(String.valueOf(targetWebServerPort)).as("port for target server is not register correctly in properties");
}
@Test
public void targetShouldReceiveExpectedCustomer() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
//assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.COMPLETED);
assertThat(endpoint.getReceivedCustomers()).hasSize(1);
Customer actualCustomer=endpoint.getReceivedCustomers().get(0);
assertThat(actualCustomer.getId()).isEqualTo(1);
assertThat(actualCustomer.getFirstName()).isEqualTo("Vincent");
assertThat(actualCustomer.getLastName()).isEqualTo("FUCHS");
}
}
With these annotations, TargetRESTSystem will be deployed in a tomcat on port 8080.
On the batch side, the component that writes is configured like this, with a property file giving target.port=8080 :
public class RestCustomerWsClient implements CustomerWsClient {
@Value("${target.host}")
private String targetHost="undefined";
@Value("${target.port}")
private String targetPort="undefined";
@Override
public void sendCustomer(Customer customerFromExcel) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForEntity("http://"+targetHost+":"+targetPort+"/integrate", customerFromExcel, null);
System.out.println("sent client integration request");
}
}
Like I said, this works well because I defined myself the port and made the value match between WebIntegrationTest annotation and the property file on the batch side.
But what if I want to make my test more robust and use a random port, because I don't want it to fail if 8080 is already used by another process ? I can use this instead :
@WebIntegrationTest(randomPort=true)
I've tried various things, but none is working because of the order in which things get loaded :
- the @Before method to put the dynamic value in properties, but I need to refresh context after, and Spring won't allow me
- I also tried with tweaking ApplicationContextInitializer / AbstractGenericContextLoader, but at the time they are called, Tomcat hasn't started yet, and I don't have the random value yet.
So basically, I would need my test to startup Tomcat, maybe deploy my hollow webservice in it, give me the random port so that I can add it in context, and then only load my TestSpecificConfiguration / CustomerBatchConfiguration.
Any idea if this is possible ? or is the WebIntegrationTest annotation really targetted at testing webapps, on which we perform ourselves http requests within the test ?
Thanks
Vincent
===== EDIT
in the latest version of the code I have committed, I'm using reflection to set the dynamic port on my component that is already loaded in Spring context. https://github.com/vincent-fuchs/spring-projects/commit/df7bf3a76fc6109fdacac21d7dd10b045ccd0458
It works, but obviously it's not the best solution. So if anbody has a clean way of doing this, that would be great !