I'm a big automated testing fan. Both work and personal, it just makes life easier. And I'm not just talking about unit testing. Integration/Acceptance/Verification/E2E Testing (Depending on what term you prefer) can be an invaluable tool, giving a whole other level of coverage to what you get from Unit Tests.
However, in order to do this you need to be able to actually run the stack that you want to test. And you need to be able to easily write the tests that you want to run against the stack.
For this level of tests, I'm a big fan of Cucumber - for which there is a Java Runner. This gives complete separation between the test definitions and the actual code that performs the logic, which means that the implementation of the tests can be changed without changing the business definition.
Spring Boot, and the Cucumber Spring integration, then makes it really easy to actually start the service as part of the test engine. The Spring context is started up once, all of the features are executed using it, and then the Spring context is shut down. It even supports starting the application listening on a random port so that there are no conflicts.
So, how do you make this work? Cucumber tests come in three different parts. There is the Test Runner, the Feature Files, and the Step Definitions.
The Test Runner is a trivial JUnit class that simply uses the Cucumber JUnit Runner, and does all of the heavy work here. Mine typically look like:
/**
* Test runner for all of the tests
*/
@RunWith(Cucumber::class)
@CucumberOptions(tags = arrayOf("~@ignore"),
format = arrayOf(
"pretty",
"html:target/site/cucumber/cucumber",
"json:target/failsafe-reports/cucumber.json"
),
strict = true)
internal class CucumberIT
Feature files then live anywhere under the same package that the test runner is in - it will find them all at runtime.
The Step Definitions are where it gets clever. Cucumber-Spring allows you to specify a Spring Context that is started up and used to build your Step Definitions. The requirement here is that you can only use one Spring Context for the entire test suite. This means that we need to have a Spring Context that includes our test beans but also imports out Spring Boot Application to run.
The way that we power this is with two annotations on one of the step definitions - it doesn't matter which one. (More on the subtleties of this in a later post). These look like:
@ContextConfiguration(locations = arrayOf("classpath:/testContext.xml"))
@SpringBootTest(classes = arrayOf(Application::class), webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
internal class SomeStepDefs {}
Finally, you need a way to actually make calls to the Spring Boot application that you are running. There are two ways of managing this, depending on whether you want to do REST calls or Web Browser.
For REST calls, you need to Autowire in a bean of type org.springframework.boot.test.web.client.TestRestTemplate
. This is, to all intents and purposes, a RestTemplate that is coded to know how to call the running Spring Boot application. It knows the base URL to call to get to the correct endpoints.
For anything else, you can use the org.springframework.boot.context.embedded.LocalServerPort
annotation to inject a value into your class contain the port the Spring Boot application is listening on. You can then use this port to write any URL you want for any purpose you want - e.g. to power Selenium.
Top comments (4)
It will be useful if you can publish a complete working example
Hi Graham,
The same configuration isn't working for me. Tries exactly the same as you have mentioned.
Thanks,
Trilok
Hi Graham,
Need your inputs. How do I execute integration test cases each time before running my application?
If you're using Maven, then the Failsafe plugin will automatically run any test classes that end with "IT". If you're using a different build tool then I'm not exactly sure, but the same idea would apply.
HTH