This post will use
to provide an integration test environment for Spring Boot applications, running at least JUnit 4.12, Spring Boot 1.4, the Failsafe plugin in the version managed by Spring Boot and the latest docker-maven-plugin.
Gerald Venzl asked for it on twitter especially in the context of integration tests with databases. In case you don’t know, Gerald and Bruno Borges are responsible for the official Oracle database docker images, which I am using at my company respectively for my upcoming talk at DOAG 2016.
Apart from Gerald asking I had several reasons to finally get this topic right. First, after upgrading my jugs site to Spring Boot 1.4 and with it, to Hibernate 5 I ran into issues with the ID generator which behaved differently than before (a million thanks to Vlad Mihalcea for his great input).
eurejug.eu is developed locally on an H2 database and runs on Pivotal Cloud Foundry in production where it uses a PostgreSQL database. So my problem was detected during the existing unit tests and broke the application in a way that wasn’t immediately obvious.
And last but not least: We at ENERKO INFORMATIK are creating mainly database centric application, some of them only 2 tier applications with a lot of SQL logic. That logic is fine for us and our customers, but automated integration testing always gives us a hard time, where I am convinced, that the following setup I developed for the Euregio JUG, will give us a lot of improvements, replacing the PostgreSQL docker image with Oracle ones. So everything you’ll read can be applied to other databases as well.
Configuring integration tests
There’s actually not much to do in a Spring Boot / Maven based application, just add the plugin like I did:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> |
I can safely omit the version number because Spring Boot has this plugin in its managed dependencies. The failsafe plugin automatically recognizes the following patterns in test classes as integration tests:
- “**/IT*.java” – includes all of its subdirectories and all Java filenames that start with “IT”.
- “**/*IT.java” – includes all of its subdirectories and all Java filenames that end with “IT”.
- “**/*ITCase.java” – includes all of its subdirectories and all Java filenames that end with “ITCase”.
The surefire plugin itself excludes them in the current version so that they aren’t run as Unit tests. I didn’t bother to move them into separate folders but that should be easy be doable by the Build Helper plugin.
Configure your containers with docker-maven-plugin
As I said before, the docker-maven-plugin has a superb documentation and is really easy to use. Here it is used to start a docker container based on the official postgresql image before the integration tests run. The integration tests are run by the failsafe plugin so its made sure that the container will be removed afterwards. See the commit for EuregJUG:
<plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.20.1</version> <executions> <execution> <id>prepare-it-database</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> <configuration> <images> <image> <name>postgres:9.5.4</name> <alias>it-database</alias> <run> <ports> <port>it-database.port:5432</port> </ports> <wait> <log>(?s)database system is ready to accept connections.*database system is ready to accept connections</log> <time>20000</time> </wait> </run> </image> </images> </configuration> </execution> <execution> <id>remove-it-database</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> |
Here it runs an existing image under the alias “it-database”. It waits either until the configured message appears in the Docker logs or the time expires (The plugin can also be used to create new images, for example based on the Oracle Docker images which is something I’ll use in my talk and publish afterwards). The container is started before the integration tests and stopped afterwards.
Also take note of the important port mapping:
. Docker maps this port to a random high port on your machine. This random port will be grabbed by the Maven plugin and assigned to the new property it-database.port
and can be used throughout the pom file. If you would use the Oracle Database image, that would 1521 in all probability.
I use the new property as an environment variable for the integration tests by adding the following to the failsafe configuration above:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <environmentVariables> <it-database.port>${it-database.port}</it-database.port> </environmentVariables> </configuration> </plugin> |
and make use of Spring Boots awesome configuration features, as you can see in this commit, where I add a file named application-it.properties
containing the following line among others:
spring.datasource.url = jdbc:postgresql://localhost:${it-database.port}/postgres
recognize the property? This way, I don’t have to hardcode the port somewhere, which would lead into problems when several builds run in parallel (for example on a CI machine). Parallel build wouldn’t be possible if Docker would map the exposed port to a fixed port on the machine running the build.
Also that file configures the data to load through:
spring.datasource.data = classpath:data-it.sql
Per default it would load a file called “data-
Writing the actual tests for a Spring Boot application
So far, that was nothing, wasn’t it? I’m really impressed how much development change the last years. Yes, Maven is still XML based, but I didn’t have to do any “fancy” things or use special scripts or whatever to a sane testing environment up and running.
The actual test I needed looks like this:
import static org.hamcrest.Matchers.is; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; import static org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase.Replace.NONE; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) @ActiveProfiles("it") public class RegistrationRepositoryIT { @Autowired private EventRepository eventRepository; @Autowired private RegistrationRepository registrationRepository; @Test public void idGeneratorsShouldWorkWithPostgreSQLAsExpected() { // data-id.sql creates on registration with id "1" final EventEntity event = this.eventRepository.findOne(1).get(); final RegistrationEntity savedRegistration = this.registrationRepository.save(new RegistrationEntity(event, "foo@bar.baz", "idGeneratorsShouldWorkWithPostgreSQLAsExpected", null, true)); Assert.assertThat(savedRegistration.getId(), is(2)); } } |
Important here are three things:
- Run the test with
@RunWith(SpringRunner.class)
and as a@DataJpaTest
. I don’t want to fire up the whole application but only JPA and the corresponding repositories. - By default, @DataJpaTest replaces all data sources by an in memory database if such is on the class path, which is exactly what I don’t want here. So I prohibit this with
@AutoConfigureTestDatabase(replace = NONE)
- Activate a profile named “it” with
@ActiveProfiles("it")
so that Spring Boot takes the application-it.properties into account mentioned earlier.
Summary
I’m really, really happy with the solution, especially regarding the fact that I’m probably the right amount late to the Docker party. While it was relatively easy to install Docker on Linux, you would have to jump through hoops to get it running under OS X and especially on Windows. Lately, Docker natively supports libvirt under OS X and HyperV under Windows 10 pro so my colleagues don’t have a reason not to use it. The pre build images works fine and even the mentioned Oracle images above build without errors on several OS X and Windows machines I tested.
Even if you don’t want to have anything to do with Docker, the docker-maven-plugin is your friend. Nothing needs to be started manually, it just works. The EuregJUG site just build flawless on my Jenkins based CI.
If this post if of use to you, I’d be happy to hear in the comments.
Update: Here’s a snippet to build and run Oracle Database instances from the official Oracle Docker files: pom.xml.
Update 2: I just noticed that this commit on the above mentioned Oracle Database Images makes them somewhat less useful for integration testing: Now the Dockerfile only installs the database software but doesn’t create the database and the container takes ages to start up… I don’t have a solution for that right now and i’m staying with my older images, even if they are bit larger.
Update 3: In some cases a log messages appears twice in a container. The Maven-Docker-Plugin supports regex for log-waits since 0.20.1, which is reflected in pom snippet now. Also regarding Update 2: One just has to do it “right”, see this article.
13 comments
Hi Michael,
Interesting approach, I also blogged about the same topic but using Spotify’s docker plugin. Created the Postgres Docker images with the DB and seeded data so that when the container starts, it’s just ready to be used in an integration test, once the test is done, container is stopped and a new one is started, no need to re-seed the data. One gotcha is that I actually needed to implement a specific TestExecutionListener to take care of the Docker containers life-cycle.
Blog post available at: http://tech.asimio.net/2016/08.....ocker.html
Best,
Orlando
So I am using your implementation of the it-database.port variable but I keep getting the following exception
java.sql.SQLException: Driver:org.postgresql.Driver@4bdd6fe0 returned null for URL:jdbc:postgresql://localhost:${it-database.port}/postgres
and I am not sure what is going on, I get that it is passing a null value when it is expecting the value for the port. What I don’t know is why it is null. I am sorry if this is something obvious and I am missing it but thought I would reach out anyway.
Hi,
That example is from a project using Spring Boot, that pom over here: https://github.com/EuregJUG-Maas-Rhine/site/blob/master/pom.xml
It seems as the URL “jdbc:postgresql://localhost:${it-database.port}/postgres” is passed as is to the JDBC driver. Spring Boots configuration mechanism should replace that value with the one from the environment that the Docker Maven Plugin creates.
Check this…
What if you want to run this integration test from intellj idea? Docker won’t start by default then.
If you used the Docker Maven plugin, just fire it up with
mvn docker:start
I ran into the problem that the docker container restarts the database after initialization, so the log line `database system is ready to accept connections` appears twice in the log. This starts the integration tests too early, so I had to change the “ configuration to:
`(?s)PostgreSQL init process complete.*database system is ready to accept connections`
Thanks for the great article!
Hi Benjamin… Yeah, I know, the regex was my idea 😉 See: https://github.com/fabric8io/docker-maven-plugin/issues/628#issuecomment-289217895
I forgot to update the text.
Thanks for your feedback!
For Linux environment you have to replace ${it-database.port} to ${it-database.port} and fix application-it.properties
Looks identical to me?!
Further to Fedor Gagarin’s post, on Mint I found that I had to remove both the – and . characters for the environment variable to work. A quick search didn’t throw up much useful advice on what characters are or are not allowed in Linux environment variables (it seems to depend on the distro), but it seems that alphanumerics plus the underscore character are the safest bet, probably without starting with a number.
Despite developing on Linux for years, for some reason this had never occurred to me and it’s not a problem I remember hitting before – I guess I’ve generally just played it safe and used ‘safe’ characters anyway. It was frustratingly difficult to identify, because the only error I saw was Spring failing to dereference ${it-database.port}, and complaining that it’s not an integer. I’d pulled it apart completely and rewritten every line several times before I had the lightbulb moment 🙁
If you’re feeling super-generous, it might not hurt to modify your example to remove the . and – from the environment variable, to save poor saps like me from wasting time in the future 🙂
(I should have also said thank you very much for the post – it’s a great summary of exactly what I was looking for!)
Thanks for your feedback. Much appreciated.
To resolve error with ${it-database.port}, just replace:
jdbc:postgresql://localhost:${it-database.port}/postgres
with
jdbc:postgresql://localhost:${DATABASE_PORT}/postgres
and
${it-database.port}
with
${it-database.port}
4 Trackbacks/Pingbacks
[…] are free. If I’d use this for integration testing or such I advice random ports as described here. Port 1521 is import for SQL*Plus, the other ones are Oracle management […]
[…] 30 minutes while talking about it? Fun! Effortless integration testing with enterprise databases? Done. Making JPA / Hibernate a tool effortless to use? Throw Spring Data JPA in the mix. The list could […]
[…] already wrote about integration testing with Docker and Maven. In that post I used the docker-maven-plugin to fire up supporting services for my […]
[…] bin ich zunächst über den Artikel Integration testing with Docker and Maven von Michael Simons gestolpert und fand den Ansatz sehr interessant, beliebige Docker Container mit dem […]
Post a Comment