{"id":533539,"date":"2024-12-16T15:50:37","date_gmt":"2024-12-16T14:50:37","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=idea&#038;p=533539"},"modified":"2024-12-17T13:46:21","modified_gmt":"2024-12-17T12:46:21","slug":"testing-spring-boot-applications-using-testcontainers","status":"publish","type":"idea","link":"https:\/\/blog.jetbrains.com\/ko\/idea\/2024\/12\/testing-spring-boot-applications-using-testcontainers","title":{"rendered":"Testing Spring Boot Applications Using Testcontainers"},"content":{"rendered":"\n<p>Testing is a crucial part of software development, verifying that a system functions as intended. Developers create unit tests to validate the behavior of individual components, isolating them from external dependencies such as file systems, databases, message brokers, and third-party APIs. However, since many applications rely on these external components, developers write integration tests to ensure an application interacts correctly with its dependencies in a more complete environment.<\/p>\n\n\n\n<p>Integration testing can be challenging if developers rely on shared environments, mock systems, or manual configuration of dependencies, all of which can lead to brittle tests and undetected issues in production.<\/p>\n\n\n\n<p><a href=\"https:\/\/java.testcontainers.org\/\" target=\"_blank\" rel=\"noopener\">Testcontainers for Java<\/a> is an open-source Java library that provides lightweight, disposable containers as test fixtures, enabling developers to run real instances of databases, message brokers, web servers, and other dependencies during testing. By leveraging Docker, Testcontainers ensures that tests run in isolated and consistent environments, independent of the host machine&#8217;s configuration. Testcontainers integrates seamlessly with popular testing libraries like JUnit and TestNG, making it easy for developers to write integration tests.<\/p>\n\n\n\n<p>This blog post covers the following topics:<\/p>\n\n\n\n<ul>\n<li>Getting started with Testcontainers<\/li>\n\n\n\n<li>Writing tests with Testcontainers and JUnit 5<\/li>\n\n\n\n<li>Testing Spring Data JPA repositories using Testcontainers<\/li>\n\n\n\n<li>Testing Spring Boot REST APIs using Testcontainers<\/li>\n<\/ul>\n\n\n\n<p>Let&#8217;s get started by setting up Testcontainers. If you already know how to do that, feel free to skip ahead to the <em><a href=\"#writing-tests-with-testcontainers-and-junit-5\" data-type=\"internal\" data-id=\"#writing-tests-with-testcontainers-and-junit-5\">Writing tests with Testcontainers and JUnit 5<\/a> <\/em>section.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting started with Testcontainers<\/h2>\n\n\n\n<p>The Testcontainers library provides a higher-level API to manage the container life cycle, extract information about running containers, and more. While it is predominantly used for testing, Testcontainers can also be used for local development.<\/p>\n\n\n\n<p>The Testcontainers library needs a container runtime to create the container instances. To find the supporting container runtimes, see <a href=\"https:\/\/java.testcontainers.org\/supported_docker_environment\/\" target=\"_blank\" rel=\"noopener\">this doc<\/a>.<\/p>\n\n\n\n<p>Let\u2019s look at how you can use the Testcontainers API to create a Redis Docker container instance using a <code>GenericContainer<\/code> abstraction.<\/p>\n\n\n\n<p>First, add the testcontainers dependency to your build file:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Gradle build.gradle\ntestImplementation 'org.testcontainers:testcontainers:1.20.4'\n\n\/\/Maven pom.xml\n&lt;dependency>\n   &lt;groupId>org.testcontainers&lt;\/groupId>\n   &lt;artifactId>testcontainers&lt;\/artifactId>\n   &lt;version>1.20.4&lt;\/version>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Now, you can use the <code>GenericContainer<\/code> API to create a container from the <code>redis:7<\/code> Docker image exposing the container\u2019s port 6379 to the host and use the container as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import org.testcontainers.containers.GenericContainer;\n\n\nGenericContainer&lt;?> container = new GenericContainer&lt;>(\"redis:7\").withExposedPorts(6379);\ncontainer.start();\nString host = container.getHost();\nint hostPort = container.getMappedPort(6379);\nSystem.out.println(\"Redis started on \" + host + \":\" + hostPort);\ncontainer.stop();<\/pre>\n\n\n\n<p>Testcontainers maps the exposed container ports to randomly available ports on the host machine so there won\u2019t be any conflicts with other containers running in parallel. You can use the <code>container.getMappedPort(port)<\/code> method to get the mapped port on the host machine.<\/p>\n\n\n\n<p>The <code>GenericContainer<\/code> provides a generic API for managing containers, but it doesn\u2019t provide any container-specific methods. To streamline working with various technologies, Testcontainers offers technology-specific modules and additional API methods. You can see the full list of available Testcontainers modules <a href=\"https:\/\/testcontainers.com\/modules\/\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n\n\n\n<p>Now, let\u2019s look at how you can use the Testcontainers PostgreSQL module and the additional methods it provides.<\/p>\n\n\n\n<p>Add the Testcontainer <code>postgresql<\/code> module dependency to your build file:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Gradle build.gradle\ntestImplementation 'org.testcontainers:postgresql:1.20.4'\n\n\/\/Maven pom.xml\n&lt;dependency>\n   &lt;groupId>org.testcontainers&lt;\/groupId>\n   &lt;artifactId>postgresql&lt;\/artifactId>\n   &lt;version>1.20.4&lt;\/version>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Now, you can use the <code>PostgreSQLContainer<\/code> class to create an instance of a PostgreSQL container:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">PostgreSQLContainer&lt;?> postgres = new PostgreSQLContainer&lt;>(\"postgres:17\");\npostgres.start();\n\nhost = postgres.getHost();\nhostPort = postgres.getMappedPort(5432);\n\/\/If you expose only one port, then you can use getFirstMappedPort()\nhostPort = postgres.getFirstMappedPort();\n\n\/\/ container-specific methods\nString jdbcUrl = postgres.getJdbcUrl();\nString username = postgres.getUsername();\nString password = postgres.getPassword();\nString databaseName = postgres.getDatabaseName();\n\npostgres.stop();<\/pre>\n\n\n\n<p><code>PostgreSQLContainer<\/code> abstracts the details of the container port number, database credentials, and readiness checks and provides you with a high-level API to interact with it. You can also see that this class provides additional methods to get information about the PostgreSQL container, such as <code>getJdbcUrl()<\/code> and <code>getDatabaseName()<\/code>.<\/p>\n\n\n\n<p><strong>NOTE:<\/strong> Even if you don\u2019t explicitly destroy the containers by calling the <code>container.stop()<\/code> method, Testcontainers will automatically destroy the container upon exiting the JVM or by using the <a href=\"https:\/\/github.com\/testcontainers\/moby-ryuk\" target=\"_blank\" rel=\"noopener\">Moby Ryuk sidecar container<\/a> behind the scenes.<\/p>\n\n\n\n<p>Now that we\u2019ve explored setting up the Testcontainers library to create, start, and stop Docker containers, as well as extract information about them, let\u2019s see how to use Testcontainers for testing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Writing tests with Testcontainers and JUnit 5<\/h2>\n\n\n\n<p>Typically, the Testcontainers library is used for integration testing in conjunction with testing libraries like JUnit. The components, such as databases and message brokers, that are required for tests will be started before running any test cases\u200b\u200b and destroyed after the test execution is completed.<\/p>\n\n\n\n<p>You can use JUnit test life cycle callback methods to start and stop the containers:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.PostgreSQLContainer;\n\nclass TestcontainersWithJunit5Callbacks {\n   static PostgreSQLContainer postgres = new PostgreSQLContainer(\"postgres:17\");\n  \n   @BeforeAll\n   static void beforeAll() {\n       postgres.start();\n       \/\/ configure your application to talk to the PostgreSQL container\n   }\n\n   @AfterAll\n   static void afterAll() {\n       postgres.stop();\n   }\n\n   @Test\n   void test1() {\n       \/\/ test code that uses postgres\n   }\n\n   @Test\n   void test2() {\n       \/\/ test code that uses postgres\n   }\n}<\/pre>\n\n\n\n<p>A common practice is starting the containers before test execution and destroying those containers after test execution is completed. The Testcontainers JUnit 5 Jupiter extension is created to simplify this process by using an annotation-based approach.<\/p>\n\n\n\n<p>First, add the following Testcontainers <code>junit-jupiter<\/code> dependency:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Gradle build.gradle\ntestImplementation 'org.testcontainers:junit-jupiter:1.20.4'\n\n\/\/Maven pom.xml\n&lt;dependency>\n   &lt;groupId>org.testcontainers&lt;\/groupId>\n   &lt;artifactId>junit-jupiter&lt;\/artifactId>\n   &lt;version>1.20.4&lt;\/version>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Now, you can write integration tests using the Testcontainers JUnit Jupiter extension:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\n@Testcontainers\nclass TestcontainersWithJuperExtension {\n  \n   @Container\n   static PostgreSQLContainer postgres =\n           new PostgreSQLContainer(\"postgres:17\");\n\n   @Test\n   void test1() {\n       \/\/ test talking to postgres\n   }\n\n   @Test\n   void test2() {\n       \/\/ test talking to postgres\n   }\n}<\/pre>\n\n\n\n<p>By adding the <code>@Testcontainers<\/code> annotation on the class and <code>@Container<\/code> annotation on the container declaration field, JUnit will automatically start the container before running tests and automatically destroy it after the test has been executed.<\/p>\n\n\n\n<p><strong>NOTE: <\/strong>When you use the <code>@Container<\/code> annotation on <code>static<\/code> container declarations, like in the example above, only one instance of that container is created for all test cases within a single test class. If you want to create a separate instance for each test case, make it a <code>non-static<\/code> field. Remember that spinning up a new container instance for every test case is not recommended, as it slows down the test execution.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Integration testing Spring Boot applications using Testcontainers<\/h2>\n\n\n\n<p>While Testcontainers can be used with any Java application, frameworks like Spring Boot provide out-of-the-box support for Testcontainers to make integration testing easier.<\/p>\n\n\n\n<p>Let\u2019s explore how you can test Spring Data JPA repositories and Spring Boot REST APIs that use a PostgreSQL database with Testcontainers.<\/p>\n\n\n\n<p>If you want to learn how to build a Spring Boot REST API with Spring Data JPA, PostgreSQL, and Flyway, read the following blog posts:<\/p>\n\n\n\n<ul>\n<li><a href=\"https:\/\/blog.jetbrains.com\/idea\/2024\/10\/how-to-build-a-crud-rest-api-using-spring-boot\/\">How to Build a CRUD REST API Using Spring Boot<\/a>&nbsp;<\/li>\n\n\n\n<li><a href=\"https:\/\/blog.jetbrains.com\/idea\/2024\/11\/how-to-use-flyway-for-database-migrations-in-spring-boot-applications\/\">How to Use Flyway for Database Migrations in Spring Boot Applications<\/a><\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p>Let\u2019s use the <a href=\"https:\/\/github.com\/sivaprasadreddy\/bookmarks\" target=\"_blank\" rel=\"noopener\">bookmarks<\/a> Spring Boot application that was created in the above blog posts and write tests for the Spring Data JPA repository and REST API using Testcontainers.<\/p>\n\n\n\n<p>First, clone the <code>flyway<\/code> branch of the bookmarks repository and create a new branch called <code>testcontainers<\/code>. The <code>flyway<\/code> branch uses Flyway database migrations to manage database schema changes.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$ git clone --branch flyway https:\/\/github.com\/sivaprasadreddy\/bookmarks.git\n$ git checkout -b testcontainers<\/pre>\n\n\n\n<p>Now, add the following Testcontainers <a href=\"https:\/\/testcontainers.com\/modules\/postgresql\/\" target=\"_blank\" rel=\"noopener\">PostgreSQL module<\/a> and junit-jupiter dependencies:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Gradle build.gradle\ntestImplementation 'org.testcontainers:postgresql'\ntestImplementation 'org.testcontainers:junit-jupiter'\n\n\/\/Maven pom.xml\n&lt;dependency>\n   &lt;groupId>org.testcontainers&lt;\/groupId>\n   &lt;artifactId>postgresql&lt;\/artifactId>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency>\n&lt;dependency>\n   &lt;groupId>org.testcontainers&lt;\/groupId>\n   &lt;artifactId>junit-jupiter&lt;\/artifactId>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Spring Boot provides slice test annotations such as <code>@DataJpaTest<\/code>, <code>@DataMongoTest<\/code>, and <code>@WebMvcTest<\/code> to test a slice of the application. By using these slice test annotations, you can write tests that load only the necessary components of the application. To write integration tests, Spring Boot\u2019s <code>@SpringBootTest<\/code> annotation bootstraps the entire application context, loading all the components.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Testing Spring Data JPA repositories<\/h3>\n\n\n\n<p>Here\u2019s the BookmarkRepository interface within the bookmarks project:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.jetbrains.bookmarks;\n\nimport org.springframework.data.jpa.repository.JpaRepository;\n\nimport java.util.List;\nimport java.util.Optional;\n\npublic interface BookmarkRepository extends JpaRepository&lt;Bookmark, Long> {\n List&lt;BookmarkInfo> findAllByOrderByCreatedAtDesc();\n\n Optional&lt;BookmarkInfo> findBookmarkById(Long id);\n}<\/pre>\n\n\n\n<p>A common mistake when testing repositories is using an in-memory database like H2 for testing while using a different type of database like PostgreSQL or Oracle Database in production. There is no guarantee that the implementation that worked with an in-memory database will work in the same way with other databases. Additionally, the in-memory database may not have all the features provided by your production database and vice versa.<\/p>\n\n\n\n<p>For example, the following is a valid query for PostgreSQL:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">INSERT INTO items(id, code, name) VALUES(?,?,?) ON CONFLICT DO NOTHING;<\/pre>\n\n\n\n<p>However, this query doesn\u2019t work with the H2 database by default. Executing the above query with the H2 database results in the following exception:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement \"INSERT INTO items (id, code, name) VALUES (?, ?, ?) ON[*] CONFLICT DO NOTHING\";\"<\/pre>\n\n\n\n<p>Furthermore, the H2 database supports the <code>ROWNUM()<\/code> function, whereas PostgreSQL does not.<\/p>\n\n\n\n<p>To ensure consistent behavior between testing and production, using the same database type is recommended. Testcontainers is useful because it facilitates this approach.<\/p>\n\n\n\n<p>Now, let\u2019s write tests for the <code>BookmarkRepository<\/code> using the <code>@DataJpaTest<\/code> slice test annotation using Testcontainers:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.jetbrains.bookmarks;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;\nimport org.springframework.test.context.DynamicPropertyRegistry;\nimport org.springframework.test.context.DynamicPropertySource;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@DataJpaTest\n@Testcontainers\nclass BookmarkRepositoryTest {\n   @Container\n   static PostgreSQLContainer&lt;?> postgres = new PostgreSQLContainer&lt;>(\"postgres:17\");\n\n   @DynamicPropertySource\n   static void configureProperties(DynamicPropertyRegistry registry) {\n       registry.add(\"spring.datasource.url\", postgres::getJdbcUrl);\n       registry.add(\"spring.datasource.username\", postgres::getUsername);\n       registry.add(\"spring.datasource.password\", postgres::getPassword);\n   }\n\n   @Autowired\n   BookmarkRepository bookmarkRepository;\n\n   @Test\n   void shouldFindBookmarkById() {\n       var bookmark = new Bookmark(\"JetBrains Blog\",\"https:\/\/blog.jetbrains.com\");\n       Long bookmarkId = bookmarkRepository.save(bookmark).getId();\n\n       var bookmarkInfo = bookmarkRepository.findBookmarkById(bookmarkId).orElseThrow();\n       assertThat(bookmarkInfo.getTitle()).isEqualTo(\"JetBrains Blog\");\n       assertThat(bookmarkInfo.getUrl()).isEqualTo(\"https:\/\/blog.jetbrains.com\");\n   }\n}<\/pre>\n\n\n\n<p>We are using the <code>@Testcontainers<\/code> and <code>@Container<\/code> annotations to start the PostgreSQL container before running tests and destroying it afterward. Once the PostgreSQL container is started, we can configure our Spring Boot application data source properties to connect to the PostgreSQL container using <code>@DynamicPropertySource<\/code> \u2013 this allows us to override the default properties.<\/p>\n\n\n\n<p>If you run the test, you can see that a PostgreSQL container starts, and the test executes against the PostgreSQL database container.<\/p>\n\n\n\n<p>Spring Boot 3.1.0 introduced ServiceConnection <a href=\"https:\/\/docs.spring.io\/spring-boot\/reference\/testing\/testcontainers.html#testing.testcontainers\" target=\"_blank\" rel=\"noopener\">support<\/a>, simplifying the usage of Testcontainers for testing and local development. To use this feature, add the following dependency:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Gradle build.gradle\ntestImplementation 'org.springframework.boot:spring-boot-testcontainers'\n\n\/\/Maven pom.xml\n&lt;dependency>\n   &lt;groupId>org.springframework.boot&lt;\/groupId>\n   &lt;artifactId>spring-boot-testcontainers&lt;\/artifactId>\n   &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Now, let\u2019s update the <code>BookmarkRepositoryTest<\/code> to use the ServiceConnection support:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import org.springframework.boot.testcontainers.service.connection.ServiceConnection;\n\n@DataJpaTest\n@Testcontainers\nclass BookmarkRepositoryTest {\n   \n   @Container\n   @ServiceConnection\n   static PostgreSQLContainer&lt;?> postgres = new PostgreSQLContainer&lt;>(\"postgres:17\");\n\n   @Autowired\n   BookmarkRepository bookmarkRepository;\n\n   @Test\n   void shouldFindBookmarkById() {\n       \/\/...\n   }\n}<\/pre>\n\n\n\n<p>By adding the <code>@ServiceConnection<\/code> annotation to the container field definition, Spring Boot will automatically configure the data source properties pointing to the PostgreSQL database container. There is no need to explicitly configure the data source properties using <code>@DynamicPropertySource<\/code>.<\/p>\n\n\n\n<p>If you create a Spring Boot project with PostgreSQL and Testcontainers dependencies, a class with the name <code>TestcontainersConfiguration<\/code> will be created under the <code>src\/test\/java<\/code> directory:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.jetbrains.bookmarks;\n\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.boot.testcontainers.service.connection.ServiceConnection;\nimport org.springframework.context.annotation.Bean;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n@TestConfiguration(proxyBeanMethods = false)\nclass TestcontainersConfiguration {\n\n   @Bean\n   @ServiceConnection\n   PostgreSQLContainer&lt;?> postgresContainer() {\n      return new PostgreSQLContainer&lt;>(DockerImageName.parse(\"postgres:17\"));\n   }\n}<\/pre>\n\n\n\n<p>If you introduce Testcontainers in an existing application, you need to manually create this class.<\/p>\n\n\n\n<p>Instead of defining the <code>PostgreSQLContainer<\/code> in each test class, you can import the <code>TestcontainersConfiguration<\/code> class and use it in your test:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@DataJpaTest\n@Import(TestcontainersConfiguration.class)\nclass BookmarkRepositoryTest {\n\n   @Autowired\n   BookmarkRepository bookmarkRepository;\n\n   @Test\n   void shouldFindBookmarkById() {\n       \/\/...\n   }\n}<\/pre>\n\n\n\n<p>The <code>PostgreSQLContainer<\/code> bean definition and the <code>@ServiceConnection<\/code> annotation defined in <code>TestcontainersConfiguration<\/code> will take care of spinning up a PostgreSQL database container and configure the application to connect to that database.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Testing Spring Boot REST APIs<\/h3>\n\n\n\n<p>While slice tests are valuable, integration tests with real dependencies, like databases and message brokers, are crucial for verifying overall system behavior.<\/p>\n\n\n\n<p>Similar to how we used Testcontainers to test Spring Data JPA repositories, you can write integration tests with the <code>@SpringBootTest<\/code> annotation and use Testcontainers to spin up the dependencies required by your application.<\/p>\n\n\n\n<p>In the bookmarks application, there is the <code>GET \/api\/bookmarks API<\/code> endpoint, which returns all the bookmarks ordered from newest to oldest.<\/p>\n\n\n\n<p>Let\u2019s write an integration test to test this endpoint:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.jetbrains.bookmarks;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.web.client.TestRestTemplate;\nimport org.springframework.context.annotation.Import;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;\n\n@SpringBootTest(webEnvironment = RANDOM_PORT)\n@Import(TestcontainersConfiguration.class)\nclass BookmarkControllerTest {\n\n   @Autowired\n   private TestRestTemplate restTemplate;\n\n   @Autowired\n   private BookmarkRepository bookmarkRepository;\n\n   @BeforeEach\n   void setUp() {\n       bookmarkRepository.deleteAllInBatch();\n   }\n\n   @Test\n   void shouldGetAllBookmarks() {\n       bookmarkRepository.save(new Bookmark(\"JetBrains Blog\",\"https:\/\/blog.jetbrains.com\"));\n       bookmarkRepository.save(new Bookmark(\"IntelliJ IDEA Blog\",\"https:\/\/blog.jetbrains.com\/idea\/\"));\n\n       Bookmark[] bookmarks = restTemplate.getForObject(\"\/api\/bookmarks\", Bookmark[].class);\n\n       assertThat(bookmarks.length).isEqualTo(2);\n       assertThat(bookmarks[0].getTitle()).isEqualTo(\"IntelliJ IDEA Blog\");\n       assertThat(bookmarks[1].getTitle()).isEqualTo(\"JetBrains Blog\");\n   }\n}<\/pre>\n\n\n\n<p>We used the <code>@SpringBootTest<\/code> annotation, which loads the entire application context and is configured to start the server on a random available port on the host machine. We also imported the <code>TestcontainersConfiguration<\/code> class so that the required dependencies (in our case, PostgreSQL) are provisioned using Testcontainers. We then autowired <code>TestRestTemplate<\/code> to make calls to our API endpoints. We also deleted all existing bookmarks before running tests using the <code>setUp()<\/code> JUnit callback method, so we have fresh data for each test run.<\/p>\n\n\n\n<p>In the test case, we saved two new bookmarks and fetched them, invoking the <code>GET \/api\/bookmarks<\/code> endpoint, and asserting the expected results.<\/p>\n\n\n\n<p>You can find the complete code <a href=\"https:\/\/github.com\/sivaprasadreddy\/bookmarks\/tree\/testcontainers\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Testcontainers helps you test applications using real dependencies instead of relying on mock objects or in-memory alternatives, giving you more confidence in your test suite and your application\u2019s behavior. Additionally, Spring Boot\u2019s out-of-the-box support for Testcontainers makes it even easier to write integration tests.<\/p>\n\n\n\n<p>Spring Boot\u2019s <code>@ServiceConnection<\/code> support is available for the most <a href=\"https:\/\/docs.spring.io\/spring-boot\/reference\/testing\/testcontainers.html#testing.testcontainers.service-connections\" target=\"_blank\" rel=\"noopener\">commonly used technologies<\/a>. Even if there is no direct support provided by Spring Boot, you can use the Testcontainers <code>GenericContainer<\/code> abstraction to work with any containerized dependency. You can find more information about Testcontainers and its features <a href=\"https:\/\/testcontainers.com\/\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n","protected":false},"author":1517,"featured_media":535188,"comment_status":"closed","ping_status":"closed","template":"","categories":[4759,5088,1280,2347],"tags":[40,155,8620,6333,7160],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/533539"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/types\/idea"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/users\/1517"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/comments?post=533539"}],"version-history":[{"count":8,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/533539\/revisions"}],"predecessor-version":[{"id":535215,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/533539\/revisions\/535215"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/media\/535188"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/media?parent=533539"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/categories?post=533539"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/tags?post=533539"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/cross-post-tag?post=533539"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}