miércoles, abril 26, 2017

Testing Spring Data + Spring Boot applications with Arquillian (Part 1)


Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store. It provides integration with several backend technologies such as JPA, Rest, MongoDB, Neo4J or Redis to cite a few.

So if you are using Spring (Boot) then Spring Data is the right choice to deal with persistence layer.

In next example you can see how simple is to use Spring Boot and Spring Data Redis.

@Controller
@EnableAutoConfiguration
public class PingPongController {
@Autowired
StringRedisTemplate redisTemplate;
@RequestMapping("/{ping}")
@ResponseBody
List<String> getPong(@PathVariable("ping") String ping) {
final ListOperations<String, String> stringStringListOperations = redisTemplate.opsForList();
final Long size = stringStringListOperations.size(ping);
return stringStringListOperations.range(ping, 0, size);
}
@RequestMapping(value="/{ping}", method = RequestMethod.POST)
ResponseEntity<?> addPong(@PathVariable("ping") String ping, @RequestBody String pong) {
final ListOperations<String, String> stringStringListOperations = redisTemplate.opsForList();
stringStringListOperations.leftPushAll(ping, pong);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.buildAndExpand(ping).toUri();
return ResponseEntity.created(location).build();
}
public static void main(String[] args) {
SpringApplication.run(PingPongController.class, args);
}
}
@Configuration
public class RedisConfiguration {
@Bean
StringRedisTemplate template(final RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}

It is important to notice that by default Spring Data Redis is configured to connect to localhost and port 6379, but you can override those values by setting system properties (spring.redis.host and spring.redis.port) or environment variables (SPRING_REDIS_HOST and SPRING_REDIS_PORT).

But now it is time to write a test for this piece of code. The main problem you might get is that you need a Redis server installed in all machines that need to execute these tests such as developers machine or Jenkins slaves. 

This is not a problem per se but when you start working on more and more projects you'll need more and more databases installed on the system, and what even can be worst not exactly the same version as required on production. 

To avoid this problem, one possible solution is using Docker and containers. So instead of relaying on having each database installed on the system, you only depends on Docker. Then the test just starts the repository container, in our case Redis, executes the test(s) and finally stops the container.

And this is where Arquillian (and Arquillian Cube) helps you on automating everything.
Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker/Docker-Machine on Windows/Mac as the default URI, so your test is portable across several Docker installations and you don't need to worry about configuring it, Arquillian Cube adapts to what you have installed.

Arquillian Cube offers three different ways to define container(s).
  • Defining a docker-compose file.
  • Defining a Container Object.
  • Using Container Object DSL.

For this post, Container Object DSL approach is the one used. To define a container to be started before executing tests and stopped after you only need to write next piece of code.

@ClassRule
public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
.withPortBinding(6379);
In this case a JUnit Rule is used to define which image should be used in the test (redis:3.2.6) and add as binding port the Redis port (6379).

The full test looks 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)
);
}
}
}
Notice that it is a simple Spring Boot test using their bits and bobs, but Arquillian Cube JUnit Rule is used in the test to start and stop the Redis image.

Last important thing to notice is that test contains an implementation of ApplicationContextInitializer so we can configure environment with Docker data (host and binding port of Redis container) so Spring Data Redis can connect to correct instance.

Last but not least build.gradle file defines required dependencies, which looks like:

buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}
}
plugins {
id "io.spring.dependency-management" version "1.0.2.RELEASE"
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
repositories {
jcenter()
}
project.version = '1.0.0'
dependencyManagement {
imports {
mavenBom 'org.jboss.arquillian:arquillian-bom:1.1.13.Final'
}
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-web:1.5.2.RELEASE"
compile 'org.springframework.boot:spring-boot-starter-data-redis:1.5.2.RELEASE'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.5.2.RELEASE'
testCompile 'junit:junit:4.12'
testCompile 'org.arquillian.cube:arquillian-cube-docker-junit-rule:1.2.0'
testCompile 'org.assertj:assertj-core:3.6.2'
}
view raw build.gradle hosted with ❤ by GitHub

You can read more about Arquillian Cube at http://arquillian.org/arquillian-cube/

We keep learning,
Alex

Hercules and his gifts, Spiderman's control, And Batman with his fists, And clearly I don't see myself upon that list (Something just like this - The Chainsmokers & Coldplay)

Music: https://www.youtube.com/watch?v=FM7MFYoylVs

lunes, abril 10, 2017

Arquillian Persistence with MongoDB and Docker


In this screencast you are going to see how you can use Arquillian Persistence Extension (https://github.com/arquillian/arquillian-extension-persistence/tree/2.0.0) and Docker to write persistence tests for MongoDB.

To manage Docker lifecycle, I have used Arquillian Cube (http://arquillian.org/arquillian-cube/) and for populating data into MongoDB, the fairly new integration between Arquillian Persistence Extension (aka APE) and NoSQLUnit (https://github.com/lordofthejars/nosql-unit).



We keep learning,
Alex.

Ridi, Pagliaccio, Sul tuo amore infranto! Ridi del duol, che t'avvelena il cor! (Vesti la giubba (Pagliacci) - Leoncavallo)
Music: https://www.youtube.com/watch?v=Z0PMq4XGtZ4