Testing Spring Data + Spring Boot applications with Arquillian (Part 2)
In previous post, I wrote about how to test Spring Data application using Docker with Arquillian Cube. The test looked like:
@RunWith(SpringRunner.class) @SpringBootTest(classes = PingPongController.class, webEnvironment = RANDOM_PORT) @ContextConfiguration(initializers = PingPongSpringBootTest.Initializer.class) public class PingPongSpringBootTest { @ClassRule public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6") .withPortBinding(6379); @Autowired TestRestTemplate restTemplate; @Test public void should_get_pongs() { // given restTemplate.postForObject("/ping", "pong", String.class); restTemplate.postForObject("/ping", "pung", String.class); // when final List<String> pings = restTemplate.getForObject("/ping", List.class); // then assertThat(pings) .hasSize(2) .containsExactlyInAnyOrder("pong", "pung"); } public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { EnvironmentTestUtils.addEnvironment("testcontainers", configurableApplicationContext.getEnvironment(), "spring.redis.host=" + redis.getIpAddress(), "spring.redis.port=" + redis.getBindPort(6379) ); } } }
This test just starts Redis container, then populate data using restTemplate and post method, then execute the logic under test (testing GET HTTP method) and finally stop the Redis container.
It is good, it works but there are several problems there:<
- The first one is that we are using REST API to prepare data set of the test. The problem here is that the test might fail not because a failure on code under test but because of the preparation of the test (insertion of data).
- The second one is that if POST endpoint changes format/location, then you need to remember to change everywhere in the tests where it is used.
- The last one is that each test should leave the environment as found before execution, so the test is isolated from all executions. The problem is that to do it in this approach you need to delete the previous elements inserted by POST. This means to add DELETE HTTP method which might not be always implemented in endpoint, or it might be restricted to some concrete users so need to deal with special authentication things.
To avoid this problem Arquillian Persistence Extension (aka APE) was created. This extensions integrates with DBUnit and Flyway for SQL databases, NoSQLUnit for No SQL databases and Postman collections for REST services so you can populate your backend before testing the real test use case and clean the persistence storage after the test is executed.
Also population data is stored inside a file, so this means that can be reused in all tests and easily changed in case of any schema update.
Let’s see example of Part 1 of the post but updating to use APE.
@RunWith(SpringRunner.class) @SpringBootTest(classes = PingPongController.class, webEnvironment = RANDOM_PORT) @ContextConfiguration(initializers = PingPongSpringBootTest.Initializer.class) public class PingPongSpringBootTest { @ClassRule public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6") .withPortBinding(6379); @Rule public ArquillianPersistenceRule arquillianPersistenceRule = new ArquillianPersistenceRule(); @Autowired TestRestTemplate restTemplate; @Redis @ArquillianResource NoSqlPopulator populator; @Test public void should_get_pongs() { // given populator.forServer(redis.getIpAddress(), redis.getBindPort(6379)) .usingDataSet("pings.json") .execute(); // when final List<String> pings = restTemplate.getForObject("/ping", List.class); // then assertThat(pings) .hasSize(2) .containsExactlyInAnyOrder("pong", "pung"); } @After public void clean_database() { populator.forServer(redis.getIpAddress(), redis.getBindPort(6379)) .clean(); } public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { EnvironmentTestUtils.addEnvironment("testcontainers", configurableApplicationContext.getEnvironment(), "spring.redis.host=" + redis.getIpAddress(), "spring.redis.port=" + redis.getBindPort(6379) ); } } }
And the file (pings.json) used for populating Redis instance with data looks like:
{ "data" : [ { "list" : [ { "key" : "ping", "values" : [ { "value" : "pong" }, { "value" : "pung" } ] } ] } ] }
Project can be found at https://github.com/arquillian-testing-microservices/pingpongbootredis
Reference: | Testing Spring Data + Spring Boot applications with Arquillian (Part 2) from our JCG partner Alex Soto at the One Jar To Rule Them All blog. |
Hi, thanks for the post!. Running the example in a windows 10 machine raise an exception about “cannot run boot2docker”. Know thats its an old executable but i cant find the way to change it. Tks in advance