Belajar Testcontainers

Reading time ~12 minutes

Apa Itu Testcontainers ?

Testcontainers adalah framework yang menyediakan ketersediaan environment dan infrastruktur untuk kebutuhan automate test.

Latar belakang dibuat nya testcontainers adalah untuk mempermudah developer pada saat mejalankan unit test tanpa perlu melakukan setup infrastruktur seperti database, message broker dan lain sebagai nya. Testcontainers nanti nya akan menjalankan kebutuhan inftrastuktur aplikasi yang dibutuhkan dengan menggunakan container (biasa nya menggunakan docker). Dengan ada nya testcontainers, maka developer dapat fokus pada pembuatan unit test dan integration test.

Untuk saat ini, testcontainers hanya memiliki module - module tertentu saja sehingga masih terdapat keterbatasan untuk module - module yang tidak common. Contoh module yang dapat kita gunakan adalah seperti database postgresql, mysql, mongodb, kafka dan lain sebagai nya. Tetapi untuk saat ini, module - module yang terdapat pada testcontainers baru di support untuk bahasa pemrograman java, noed js, go dan .NET, berikut list module - module nya.

Setup Testcontainers Pada Spring Boot

Pada artikel ini, penulis akan mencoba mendemokan dengan menggunakan spring boot. Secara default, spring boot sudah support dengan testcontainers dan juga integration test sehingga dapat mempermudah developer dalam menggunakan module - module testcontainers. Seperti biasa, silahkan buka spring initializr. lalu isi seperti berikut

Screenshot from 2023-09-12 22-07-20.png

Lalu setelah selesai, silahkan download dan buka dengan IDE. Silahkan buka file pom.xml lalu tambahkan dependecy rest-assured seperti berikut

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.belajar.testcontainers</groupId>
    <artifactId>belajar-testcontainers</artifactId>
    <version>1.0.0</version>
    <name>belajar-testcontainers</name>
    <description>belajar testcontainers</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-testcontainers</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Silahkan buka file application.properties di dalam folder resources lalu ubah seperti berikut

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/belajar_testcontainers
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.show-sql=true
spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=create-drop

Lalu pada folder test, silahkan buat sebuah class TestBelajarTestcontainersApplication dan juga file application.properties pada folder resources lalu ubah seperti berikut

spring.jpa.show-sql=true
spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=create-drop

Membuat Domain Class

Domain class digunakan untuk keperluan mapping dari class ke table yang terdapat pada database. Silahkan buat sebuah package domain lalu silahkan buat class Product di dalam package tersebut dan ubah codingan nya seperti berikut.

package com.belajar.testcontainers.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.UuidGenerator;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.UUID;

@Entity
@Table(name = "tb_product")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product implements Serializable {
    @Id
    @UuidGenerator
    private UUID id;

    private String name;

    private BigDecimal price;

    private Integer quantity;
}

Membuat Repository Class

Repository class digunakan untuk process query ke database. Silahkan buat sebuah package repository lalu silahkan buat class ProductRepository di dalam package tersebut dan ubah codingan nya seperti berikut.

package com.belajar.testcontainers.repository;

import com.belajar.testcontainers.domain.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.UUID;

public interface ProductRepository extends JpaRepository<Product, UUID> {
}

Membuat Service Class

Service class digunakan untuk logic bisnis. Silahkan buat sebuah package service dan Service.impl lalu silahkan buat class ProductService di dalam package tersebut dan ubah codingan nya seperti berikut.

package com.belajar.testcontainers.service;

import com.belajar.testcontainers.domain.Product;

import java.util.Optional;
import java.util.UUID;

public interface ProductService {
    Product save(Product product);

    Optional<Product> findOne(UUID id);
}

Lalu buat sebuah class ProductServiceImpl di dalam package Service.impl dan ubah codingan nya seperti berikut.

package com.belajar.testcontainers.service.impl;

import com.belajar.testcontainers.domain.Product;
import com.belajar.testcontainers.repository.ProductRepository;
import com.belajar.testcontainers.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.UUID;

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public Product save(Product product) {
        return productRepository.save(product);
    }

    @Override
    public Optional<Product> findOne(UUID id) {
        return productRepository.findById(id);
    }
}

Membuat Controller Class

Controller class digunakan untuk menerima request atau traffic dari client. Silahkan buat sebuah package controller lalu silahkan buat class ProductController di dalam package tersebut dan ubah codingan nya seperti berikut.

package com.belajar.testcontainers.controller;

import com.belajar.testcontainers.domain.Product;
import com.belajar.testcontainers.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@RequestMapping(value = "/api")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping(value = "/products/{id}")
    public ResponseEntity<?> findOne(@PathVariable("id") UUID id) {
        return new ResponseEntity<>(productService.findOne(id), HttpStatus.OK);
    }

    @PostMapping(value = "/products")
    public ResponseEntity<?> save(@RequestBody Product product) {
        return new ResponseEntity<>(productService.save(product), HttpStatus.OK);
    }

}

Membuat Testcontainers Class

Silahkan buka class TestBelajarTestcontainersApplication di dalam folder test, lalu tambahkan code berikut

package com.belajar.testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestConfiguration(proxyBeanMethods = false)
public class TestBelajarTestcontainersApplication {

}

fungsi dari annotation @SpringBootTest untuk menjalankan test pada spring boot, lalu spring boot akan dijalankan dengan menggunakan port random, sedangkan fungsi dari annotation @TestConfiguration digunakan untuk membuat unit test pada spring boot, melakukan override pada bean spring boot dan membuat bean baru sesuai dengan kebutuhan unit test. Selanjutnya untuk testcontainers pada artikel ini, kita akan menggunakan module postgresql, untuk dapat menjalankan module tersebut silahkan tambahkan code berikut.

package com.belajar.testcontainers;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestConfiguration(proxyBeanMethods = false)
public class TestBelajarTestcontainersApplication {

    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
            "postgres:alpine"
    );

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @BeforeAll
    static void beforeAll() {
        postgres.start();
    }

    @AfterAll
    static void afterAll() {
        postgres.stop();
    }
}

Pada codingan diatas dapat dilihat bahwa penulis mendeklarasikan PostgreSQLContainer dimana image yang digunakan adalah postgres:alpine. Kemudian karena postgresql ini dijalankan pada testcontainer maka spring membutuhkan properties seperti url, username dan password sehingga class DynamicPropertyRegistry dapat mereplace properties tersebut. Pada annotation @BeforeAll dan @AfterAll berfungsi untuk menjalankan dan mematikan postgresql. Selanjutkan kita memerlukan setup untuk kebutuhan REST Assured dengan menambahkan code berikut

package com.belajar.testcontainers;

import io.restassured.RestAssured;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestConfiguration(proxyBeanMethods = false)
public class TestBelajarTestcontainersApplication {

    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
            "postgres:alpine"
    );

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @BeforeAll
    static void beforeAll() {
        postgres.start();
    }

    @LocalServerPort
    private Integer port;

    @BeforeEach
    void setUp() {
        RestAssured.baseURI = "http://localhost:" + port;
    }

    @AfterAll
    static void afterAll() {
        postgres.stop();
    }
}

Pada bagian annotation @LocalServerPort berfungsi untuk mengambil port spring boot yang akan berjalan pada saat unit test berjalan. Annotation @BeforeEach berfungsi untuk melakukan setup pada RestAssured setelah spring boot running dengan port random. Langkah selanjutnya silahkan tambahkan code berikut untuk melakukan test untuk save dan findOne ke database.

package com.belajar.testcontainers;

import com.belajar.testcontainers.domain.Product;
import com.belajar.testcontainers.repository.ProductRepository;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;

import java.math.BigDecimal;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestConfiguration(proxyBeanMethods = false)
public class TestBelajarTestcontainersApplication {

    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
            "postgres:alpine"
    );

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @LocalServerPort
    private Integer port;

    @BeforeAll
    static void beforeAll() {
        postgres.start();
    }

    @BeforeEach
    void setUp() {
        RestAssured.baseURI = "http://localhost:" + port;
    }

    @AfterAll
    static void afterAll() {
        postgres.stop();
    }

    @Autowired
    private ProductRepository productRepository;

    @Test
    void save() {
        var product = Product.builder()
                .name("aqua")
                .price(BigDecimal.valueOf(1000L))
                .quantity(5)
                .build();

        RestAssured.given()
                .contentType(ContentType.JSON)
                .body(product)
                .post("/api/products")
                .then()
                .statusCode(200)
                .body("name", Matchers.is(product.getName()));
    }


    @Test
    void testFindOne() {
        var product = Product.builder()
                .name("aqua")
                .price(BigDecimal.valueOf(1000L))
                .quantity(5)
                .build();

        var result = productRepository.save(product);

        RestAssured.given()
                .contentType(ContentType.JSON)
                .when()
                .get("/api/products/" + result.getId())
                .then()
                .statusCode(200)
                .body("name", Matchers.is(result.getName()));
    }
}

Lalu selanjutnya silahkan jalankan test nya dengan menggunakan command

mvn clean test

maka hasil nya akan seperti berikut

[INFO] Scanning for projects...
[INFO] 
[INFO] ---------< com.belajar.testcontainers:belajar-testcontainers >----------
[INFO] Building belajar-testcontainers 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- clean:3.2.0:clean (default-clean) @ belajar-testcontainers ---
[INFO] Deleting /home/rizki/Documents/project/belajar-project/spring-project/belajar-testcontainers/target
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ belajar-testcontainers ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] 
[INFO] --- compiler:3.11.0:compile (default-compile) @ belajar-testcontainers ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 6 source files with javac [debug release 17] to target/classes
[INFO] 
[INFO] --- resources:3.3.1:testResources (default-testResources) @ belajar-testcontainers ---
[INFO] Copying 1 resource from src/test/resources to target/test-classes
[INFO] 
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ belajar-testcontainers ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 1 source file with javac [debug release 17] to target/test-classes
[INFO] 
[INFO] --- surefire:3.0.0:test (default-test) @ belajar-testcontainers ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.belajar.testcontainers.TestBelajarTestcontainersApplication
13:36:39.856 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.belajar.testcontainers.TestBelajarTestcontainersApplication]: TestBelajarTestcontainersApplication does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
13:36:39.931 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.belajar.testcontainers.BelajarTestcontainersApplication for test class com.belajar.testcontainers.TestBelajarTestcontainersApplication
13:36:40.050 [main] INFO org.testcontainers.utility.ImageNameSubstitutor -- Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
13:36:40.188 [main] INFO org.testcontainers.dockerclient.DockerClientProviderStrategy -- Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
13:36:40.358 [main] INFO org.testcontainers.dockerclient.DockerClientProviderStrategy -- Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
13:36:40.359 [main] INFO org.testcontainers.DockerClientFactory -- Docker host IP address is localhost
13:36:40.372 [main] INFO org.testcontainers.DockerClientFactory -- Connected to docker: 
  Server Version: 24.0.6
  API Version: 1.43
  Operating System: Ubuntu 22.04.3 LTS
  Total Memory: 14812 MB
13:36:40.412 [main] INFO tc.testcontainers/ryuk:0.5.1 -- Creating container for image: testcontainers/ryuk:0.5.1
13:36:40.513 [main] INFO tc.testcontainers/ryuk:0.5.1 -- Container testcontainers/ryuk:0.5.1 is starting: 5bd8ac42d26ceb6ccbf70fef204729d955ad56c08787726138805e80d5c10639
13:36:40.848 [main] INFO tc.testcontainers/ryuk:0.5.1 -- Container testcontainers/ryuk:0.5.1 started in PT0.470571487S
13:36:40.852 [main] INFO org.testcontainers.utility.RyukResourceReaper -- Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
13:36:40.852 [main] INFO org.testcontainers.DockerClientFactory -- Checking the system...
13:36:40.853 [main] INFO org.testcontainers.DockerClientFactory -- ✔︎ Docker server version should be at least 1.6.0
13:36:40.856 [main] INFO tc.postgres:alpine -- Pulling docker image: postgres:alpine. Please be patient; this may take some time but only needs to be done once.
13:36:43.815 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Starting to pull image
13:36:43.826 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  0 pending,  0 downloaded,  0 extracted, (0 bytes/0 bytes)
13:36:44.968 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  7 pending,  1 downloaded,  0 extracted, (34 KB/? MB)
13:36:45.018 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  6 pending,  2 downloaded,  0 extracted, (34 KB/? MB)
13:36:46.230 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  5 pending,  3 downloaded,  0 extracted, (2 MB/? MB)
13:36:46.828 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  4 pending,  4 downloaded,  0 extracted, (4 MB/? MB)
13:36:46.885 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  4 pending,  4 downloaded,  1 extracted, (4 MB/? MB)
13:36:46.895 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  4 pending,  4 downloaded,  2 extracted, (4 MB/? MB)
13:36:46.902 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  4 pending,  4 downloaded,  3 extracted, (4 MB/? MB)
13:36:47.400 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  3 pending,  5 downloaded,  3 extracted, (5 MB/? MB)
13:36:50.222 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  2 pending,  6 downloaded,  3 extracted, (9 MB/? MB)
13:37:16.964 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  3 extracted, (88 MB/? MB)
13:37:18.064 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  4 extracted, (89 MB/? MB)
13:37:18.074 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  5 extracted, (89 MB/? MB)
13:37:18.081 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  6 extracted, (89 MB/? MB)
13:37:18.089 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  7 extracted, (89 MB/? MB)
13:37:18.097 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pulling image layers:  1 pending,  7 downloaded,  8 extracted, (89 MB/? MB)
13:37:18.106 [docker-java-stream-1567680985] INFO tc.postgres:alpine -- Pull complete. 8 layers, pulled in 34s (downloaded 89 MB at 2 MB/s)
13:37:18.111 [main] INFO tc.postgres:alpine -- Creating container for image: postgres:alpine
13:37:18.280 [main] INFO tc.postgres:alpine -- Container postgres:alpine is starting: de1c5c36fa428724411fda32a1d53051304d5001d630431dd0427957152c58e0
13:37:19.447 [main] INFO tc.postgres:alpine -- Container postgres:alpine started in PT38.594374044S
13:37:19.449 [main] INFO tc.postgres:alpine -- Container is started (JDBC URL: jdbc:postgresql://localhost:32773/test?loggerLevel=OFF)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.3)

2023-09-13T13:37:19.624+07:00  INFO 18120 --- [           main] b.t.TestBelajarTestcontainersApplication : Starting TestBelajarTestcontainersApplication using Java 17.0.8 with PID 18120 (started by rizki in /home/rizki/Documents/project/belajar-project/spring-project/belajar-testcontainers)
2023-09-13T13:37:19.625+07:00  INFO 18120 --- [           main] b.t.TestBelajarTestcontainersApplication : No active profile set, falling back to 1 default profile: "default"
2023-09-13T13:37:20.112+07:00  INFO 18120 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-09-13T13:37:20.150+07:00  INFO 18120 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 34 ms. Found 1 JPA repository interfaces.
2023-09-13T13:37:20.515+07:00  INFO 18120 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 0 (http)
2023-09-13T13:37:20.527+07:00  INFO 18120 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-09-13T13:37:20.527+07:00  INFO 18120 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.12]
2023-09-13T13:37:20.591+07:00  INFO 18120 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-09-13T13:37:20.593+07:00  INFO 18120 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 841 ms
2023-09-13T13:37:20.708+07:00  INFO 18120 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-09-13T13:37:20.733+07:00  INFO 18120 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.7.Final
2023-09-13T13:37:20.735+07:00  INFO 18120 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2023-09-13T13:37:20.811+07:00  INFO 18120 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-13T13:37:20.882+07:00  INFO 18120 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2023-09-13T13:37:20.890+07:00  INFO 18120 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-09-13T13:37:20.989+07:00  INFO 18120 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@5dd6f517
2023-09-13T13:37:20.990+07:00  INFO 18120 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-09-13T13:37:21.107+07:00  INFO 18120 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-13T13:37:21.420+07:00  INFO 18120 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    drop table if exists tb_product cascade
2023-09-13T13:37:21.430+07:00  WARN 18120 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Warning Code: 0, SQLState: 00000
2023-09-13T13:37:21.430+07:00  WARN 18120 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : table "tb_product" does not exist, skipping
Hibernate: 
    create table tb_product (
        price numeric(38,2),
        quantity integer,
        id uuid not null,
        name varchar(255),
        primary key (id)
    )
2023-09-13T13:37:21.439+07:00  INFO 18120 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-09-13T13:37:21.612+07:00  WARN 18120 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-09-13T13:37:21.879+07:00  INFO 18120 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 36553 (http) with context path ''
2023-09-13T13:37:21.887+07:00  INFO 18120 --- [           main] b.t.TestBelajarTestcontainersApplication : Started TestBelajarTestcontainersApplication in 2.421 seconds (process running for 42.534)
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
Hibernate: 
    insert 
    into
        tb_product
        (name,price,quantity,id) 
    values
        (?,?,?,?)
2023-09-13T13:37:22.878+07:00  INFO 18120 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-09-13T13:37:22.878+07:00  INFO 18120 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-09-13T13:37:22.881+07:00  INFO 18120 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
Hibernate: 
    select
        p1_0.id,
        p1_0.name,
        p1_0.price,
        p1_0.quantity 
    from
        tb_product p1_0 
    where
        p1_0.id=?
Hibernate: 
    insert 
    into
        tb_product
        (name,price,quantity,id) 
    values
        (?,?,?,?)
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 44.121 s - in com.belajar.testcontainers.TestBelajarTestcontainersApplication
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  46.919 s
[INFO] Finished at: 2023-09-13T13:37:23+07:00
[INFO] ------------------------------------------------------------------------

Dari log diatas, dapat dilihat bahwa test dijalankan secara sukses dan dapat dilihat terdapat log dimana maven melakukan pull docker image lalu menjalankan testcontainer image tersebut dan kemudian unit test dapat dijalankans secara lancar.

Sekian artikel mengenai Belajar testcontainers, untuk source code diatas dapat anda akses di belajar testcontainers. Jika ada saran dan komentar silahkan isi dibawah dan terima kasih :).

Instalasi Dan Setup EMQX

Instalasi Dan Setup EMQX Continue reading

Belajar Projections Pada Spring Data JPA

Published on November 04, 2023

Belajar CQRS

Published on February 01, 2023