From e3e7769f8807287967f06e9b1c8a8c71eb123869 Mon Sep 17 00:00:00 2001 From: Philip Riecks Date: Sat, 9 Jun 2018 23:53:59 +0200 Subject: [PATCH] add current codebase for testcontainers --- README.md | 1 + testcontainers/pom.xml | 10 ++- .../NoPersonFoundException.java | 12 +++ .../rieckpil/blog/testcontainers/Person.java | 16 ++++ .../blog/testcontainers/PersonRepository.java | 6 ++ .../testcontainers/PersonsController.java | 46 +++++++++++ .../src/main/resources/application.properties | 6 +- .../migration/V001__CREATE_PERSON_TABLE.sql | 4 + .../CreatePersonIntegrationTest.java | 69 +++++++++++++++++ .../DeletePersonIntegrationTest.java | 57 ++++++++++++++ .../GetAllPersonsIntegrationTest.java | 66 ++++++++++++++++ .../GetPersonByIdIntegrationTest.java | 77 +++++++++++++++++++ .../TestcontainersApplicationTests.java | 20 ++++- .../resources/testdata/FILL_FOUR_PERSONS.sql | 4 + 14 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 testcontainers/src/main/java/de/rieckpil/blog/testcontainers/NoPersonFoundException.java create mode 100644 testcontainers/src/main/java/de/rieckpil/blog/testcontainers/Person.java create mode 100644 testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonRepository.java create mode 100644 testcontainers/src/main/java/de/rieckpil/blog/testcontainers/PersonsController.java create mode 100644 testcontainers/src/main/resources/db/migration/V001__CREATE_PERSON_TABLE.sql create mode 100644 testcontainers/src/test/java/de/rieckpil/blog/testcontainers/CreatePersonIntegrationTest.java create mode 100644 testcontainers/src/test/java/de/rieckpil/blog/testcontainers/DeletePersonIntegrationTest.java create mode 100644 testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetAllPersonsIntegrationTest.java create mode 100644 testcontainers/src/test/java/de/rieckpil/blog/testcontainers/GetPersonByIdIntegrationTest.java create mode 100644 testcontainers/src/test/resources/testdata/FILL_FOUR_PERSONS.sql diff --git a/README.md b/README.md index aca3e9d..10e40e8 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 848ca14..e0168ae 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 0000000..fc857f8 --- /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 0000000..b4387e1 --- /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 0000000..0019472 --- /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 0000000..58f53c0 --- /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 2e35994..d5f93fb 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 0000000..7d1c700 --- /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 0000000..e982a5b --- /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 0000000..03d06b2 --- /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 0000000..22f6956 --- /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 0000000..84c9804 --- /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 0fa76af..aea218d 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 0000000..61a8836 --- /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 -- 2.26.2