diff --git a/README.md b/README.md
index aca3e9d8a47e8df302698748de03b594856e8c54..10e40e864de03743574782d9b7ff062cb16957ec 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,4 @@
## HOWTO's:
* Expose git information with Spring Boot’s Actuator ([Blog](https://rieckpil.de/howto-expose-git-information-with-spring-boots-actuator/), [Sources](https://github.com/rieckpil/blog-tutorials/tree/master/expose-git-information-actuator))
+* Write Spring Boot integration tests with a ‘real’ database ([Blog](https://rieckpil.de/howto-write-spring-boot-integration-tests-with-a-real-database/), [Sources](https://github.com/rieckpil/blog-tutorials/tree/master/testcontainers))
diff --git a/testcontainers/pom.xml b/testcontainers/pom.xml
index 848ca145e33865e831cd9586846e7c954ccab213..e0168ae5fb36f5cafe5db93c29c783cd005e9575 100644
--- a/testcontainers/pom.xml
+++ b/testcontainers/pom.xml
@@ -33,7 +33,15 @@
org.springframework.boot
spring-boot-starter-web
-
+
+ org.flywaydb
+ flyway-core
+
+
+ org.testcontainers
+ postgresql
+ 1.7.3
+
org.postgresql
postgresql
diff --git a/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/NoPersonFoundException.java b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/NoPersonFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc857f8f74192bae39c3b0db903227e250ea3c5c
--- /dev/null
+++ b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/NoPersonFoundException.java
@@ -0,0 +1,12 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.NOT_FOUND)
+public class NoPersonFoundException extends RuntimeException {
+
+ public NoPersonFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/Person.java b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/Person.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4387e158f8a28b92015ff5aadad3385b9ac0e0e
--- /dev/null
+++ b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/Person.java
@@ -0,0 +1,16 @@
+package de.rieckpil.blog.testcontainers;
+
+import lombok.Data;
+
+import javax.persistence.*;
+
+@Entity
+@Data
+public class Person {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+}
diff --git a/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonRepository.java b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..00194728bcdf8a4306c4426ab5f2c5a8257d671e
--- /dev/null
+++ b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonRepository.java
@@ -0,0 +1,6 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PersonRepository extends JpaRepository {
+}
diff --git a/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonsController.java b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..58f53c02cb7050be6ecfdb44346b53ecc1106be1
--- /dev/null
+++ b/testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonsController.java
@@ -0,0 +1,46 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/persons")
+public class PersonsController {
+
+ private final PersonRepository personRepository;
+
+ public PersonsController(PersonRepository personRepository) {
+ this.personRepository = personRepository;
+ }
+
+ @GetMapping
+ public List getAllPersons() {
+ return personRepository.findAll();
+ }
+
+ @GetMapping("/{id}")
+ public Person getPersonById(@PathVariable("id") Long id) {
+ return personRepository.findById(id).orElseThrow(() -> new NoPersonFoundException("Person with id:" + id +
+ " not found"));
+ }
+
+ @PostMapping
+ public Person createNewPerson(@RequestBody Person person) {
+
+ Person newPerson = new Person();
+ newPerson.setName(person.getName());
+
+ personRepository.save(newPerson);
+
+ return newPerson;
+ }
+
+ @DeleteMapping("/{id}")
+ public void deletePersonById(@PathVariable("id") Long id) {
+
+ personRepository.deleteById(id);
+
+ }
+}
diff --git a/testcontainers/src/main/resources/application.properties b/testcontainers/src/main/resources/application.properties
index 2e3599460cb5bc41d219746592195b4712301545..d5f93fb57ccacc4d76c14763a39469ce90080f37 100644
--- a/testcontainers/src/main/resources/application.properties
+++ b/testcontainers/src/main/resources/application.properties
@@ -1 +1,5 @@
-spring.jpa.hibernate.ddl-auto=
\ No newline at end of file
+spring.datasource.url=jdbc:postgresql://localhost:5432/test
+spring.datasource.password=postgres
+spring.datasource.username=postgres
+spring.jpa.hibernate.ddl-auto=validate
+spring.flyway.enabled=true
\ No newline at end of file
diff --git a/testcontainers/src/main/resources/db/migration/V001__CREATE_PERSON_TABLE.sql b/testcontainers/src/main/resources/db/migration/V001__CREATE_PERSON_TABLE.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7d1c700afc294902cc18e629f828926ca2c8c9c6
--- /dev/null
+++ b/testcontainers/src/main/resources/db/migration/V001__CREATE_PERSON_TABLE.sql
@@ -0,0 +1,4 @@
+CREATE TABLE person (
+ id BIGSERIAL PRIMARY KEY,
+ name VARCHAR(255)
+);
\ No newline at end of file
diff --git a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/CreatePersonIntegrationTest.java b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/CreatePersonIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e982a5bc9e83f36ab2eea97db72bc84c16f0929c
--- /dev/null
+++ b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/CreatePersonIntegrationTest.java
@@ -0,0 +1,69 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Transactional
+public class CreatePersonIntegrationTest {
+
+ @ClassRule
+ public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
+ .withUsername("inmemory");
+
+ @LocalServerPort
+ private int localPort;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ public TestRestTemplate testRestTemplate = new TestRestTemplate();
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
+ System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
+
+ }
+
+ @Test
+ public void testRestEndpointForAllPersons() {
+
+ Map requestParams = new HashMap();
+
+ Person requestBody = new Person();
+ requestBody.setName("rieckpil");
+
+ assertEquals(0, personRepository.findAll().size());
+
+ ResponseEntity result = testRestTemplate.postForEntity("http://localhost:" + localPort +
+ "/api/persons", requestBody, Person.class, requestParams);
+
+ assertNotNull(result);
+ assertNotNull(result.getBody().getId());
+ assertEquals("rieckpil", result.getBody().getName());
+ assertEquals(1, personRepository.findAll().size());
+ assertEquals("rieckpil", personRepository.findAll().get(0).getName());
+
+ }
+
+}
diff --git a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/DeletePersonIntegrationTest.java b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/DeletePersonIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..03d06b24bc1c31b90c3aa4a7754e2d0b7e6afa69
--- /dev/null
+++ b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/DeletePersonIntegrationTest.java
@@ -0,0 +1,57 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Transactional
+public class DeletePersonIntegrationTest {
+
+ @ClassRule
+ public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
+ .withUsername("inmemory");
+
+ @LocalServerPort
+ private int localPort;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ public TestRestTemplate testRestTemplate = new TestRestTemplate();
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ System.out.println(postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
+ System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
+
+ }
+
+ @Test
+ @Sql("/testdata/FILL_FOUR_PERSONS.sql")
+ public void testDeletePerson() {
+
+ testRestTemplate.delete("http://localhost:" + localPort +
+ "/api/persons/1");
+
+ assertEquals(3, personRepository.findAll().size());
+ assertFalse(personRepository.findAll().contains("Phil"));
+
+ }
+}
diff --git a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetAllPersonsIntegrationTest.java b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetAllPersonsIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..22f6956dfb309aa1422612e143a64984bc551bcf
--- /dev/null
+++ b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetAllPersonsIntegrationTest.java
@@ -0,0 +1,66 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Transactional
+public class GetAllPersonsIntegrationTest {
+
+ @ClassRule
+ public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
+ .withUsername("inmemory");
+
+ @LocalServerPort
+ private int localPort;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ public TestRestTemplate testRestTemplate = new TestRestTemplate();
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ System.out.println(postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
+ System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
+
+ }
+
+ @Test
+ @Sql("/testdata/FILL_FOUR_PERSONS.sql")
+ public void testGetAllPersons() {
+
+ ResponseEntity result = testRestTemplate.getForEntity("http://localhost:" + localPort +
+ "/api/persons", Person[].class);
+
+ List resultList = Arrays.asList(result.getBody());
+
+ assertEquals(4, resultList.size());
+ assertTrue(resultList.stream().map(p -> p.getName()).collect(Collectors.toList()).containsAll(Arrays.asList
+ ("Mike", "Phil", "Duke", "Tom")));
+
+ }
+
+}
diff --git a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetPersonByIdIntegrationTest.java b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetPersonByIdIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..84c980406f4363d46f6b66ad13a0a9e9c25bcd40
--- /dev/null
+++ b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetPersonByIdIntegrationTest.java
@@ -0,0 +1,77 @@
+package de.rieckpil.blog.testcontainers;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Transactional
+public class GetPersonByIdIntegrationTest {
+
+ @ClassRule
+ public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
+ .withUsername("inmemory");
+
+ @LocalServerPort
+ private int localPort;
+
+ @Autowired
+ private PersonRepository personRepository;
+
+ public TestRestTemplate testRestTemplate = new TestRestTemplate();
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ System.out.println(postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
+ System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
+
+ }
+
+ @Test
+ @Sql("/testdata/FILL_FOUR_PERSONS.sql")
+ public void testExistingPersonById() {
+
+ ResponseEntity result = testRestTemplate.getForEntity("http://localhost:" + localPort +
+ "/api/persons/1", Person.class);
+
+
+ assertEquals(HttpStatus.OK, result.getStatusCode());
+ assertEquals("Phil", result.getBody().getName());
+ assertEquals(1l, result.getBody().getId().longValue());
+
+
+ }
+
+ @Test
+ public void testNotExistingPersonByIdShouldReturn404() {
+
+ personRepository.deleteAll();
+
+ ResponseEntity result = testRestTemplate.getForEntity("http://localhost:" + localPort +
+ "/api/persons/42", Person.class);
+
+ assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode());
+ assertNull(result.getBody().getName());
+ assertNull(result.getBody().getId());
+
+ }
+}
diff --git a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/TestcontainersApplicationTests.java b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/TestcontainersApplicationTests.java
index 0fa76af455ca5faa53c60b62c767e895ca3315fc..aea218d5cb5e74d58fb7e6bfafa01769434c09a8 100644
--- a/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/TestcontainersApplicationTests.java
+++ b/testcontainers/src/test/java/de/rieckpil/blog/testcontainers/TestcontainersApplicationTests.java
@@ -1,14 +1,32 @@
package de.rieckpil.blog.testcontainers;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.PostgreSQLContainer;
@RunWith(SpringRunner.class)
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Transactional
public class TestcontainersApplicationTests {
+ @ClassRule
+ public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory")
+ .withUsername("inmemory");
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
+ System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
+ System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
+
+ }
+
@Test
public void contextLoads() {
}
diff --git a/testcontainers/src/test/resources/testdata/FILL_FOUR_PERSONS.sql b/testcontainers/src/test/resources/testdata/FILL_FOUR_PERSONS.sql
new file mode 100644
index 0000000000000000000000000000000000000000..61a8836112b3100078066604bea0eedfd6c5cac0
--- /dev/null
+++ b/testcontainers/src/test/resources/testdata/FILL_FOUR_PERSONS.sql
@@ -0,0 +1,4 @@
+INSERT INTO person VALUES (1, 'Phil');
+INSERT INTO person VALUES (2, 'Mike');
+INSERT INTO person VALUES (3, 'Duke');
+INSERT INTO person VALUES (4, 'Tom');
\ No newline at end of file