-
Notifications
You must be signed in to change notification settings - Fork 0
TestContainer
Automated database follow these steps:
- create a container (manually or automatically) ;
- create a schema (using Model using Hibernate, or using DB version control, eg. Liquibase) ;
- cleaning table to be used ;
- insert data (test given) ;
- execute SUT ;
- read data back ;
- assert.
Time is consumed in :
- starting container ;
- creating schema;
- performing DML.
Time can be saved if, for several tests :
- the container is started only once;
- schema is created only once;
- DML queries are fast.
In-memory database speed up things in:
- starting quickly;
- executing DML fast, not using fs.
If using testcontainers :
- container creation is fast (< 5 seconds), but you may create it several times ;
- DML will be faster than using out-of-the-box PostgreSQL image because fs is not used, with
fysnc=off option
; - but if you can create schema once, you can save much time.
So, if you want quick tests, you'll have to implement kind of singleton.
This may be quite complex, cause Hibernate schema creation from model hbm2ddl
cannot be used programmatically. You'll have to trigger your DB versioning tool, which is slower. But if you do it only once, and run many tests in your workday, it may be a good bet.
2 ways:
In test/resources/application.yml
spring:
first-datasource:
url: jdbc:tc:postgresql:16:///first
second-datasource:
url: jdbc:tc:postgresql:16:///second
You can add options, like :
spring:
first-datasource:
url: jdbc:tc:postgresql:16:///first?TC_REUSABLE=true?TC_TMPFS=/testtmpfs:rw
Reuse: check tile it takes to start up container using logging.
You can connect using
# get port
docker ps
psql --dbname "host=localhost port=$PORT user=test password=test dbname=postgres"
Example with two datasources, with all tests sharing the same instance.
testImplementation "org.testcontainers:testcontainers"
testImplementation "org.testcontainers:postgresql"
testImplementation "org.testcontainers:junit-jupiter"
In test/resources/application.yml
Remove all datasources
spring
Deactivate liquibase, as it will look for a datasource at startup if active
spring
liquibase:
enabled: false
Create extension to start containers and tell Spring to use them as datasource
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import liquibase.Contexts;
import liquibase.LabelExpression;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.containers.PostgreSQLContainer;
public class StartDatabaseAndCreateSchema implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws LiquibaseException, SQLException {
PostgreSQLContainer firstContainer =
(PostgreSQLContainer)
new PostgreSQLContainer("postgres:16-alpine")
.withDatabaseName("first")
.withUsername("user")
.withPassword("password")
.withExposedPorts(5432)
.withReuse(true);
PostgreSQLContainer secondContainer =
(PostgreSQLContainer)
new PostgreSQLContainer("postgres:16-alpine")
.withDatabaseName("second")
.withUsername("user")
.withPassword("password")
.withExposedPorts(5432)
.withReuse(true);
firstContainer.start();
secondContainer.start();
updateDataSourceProps("first-datasource", firstContainer);
updateDataSourceProps("second-datasource", secondContainer);
System.out.println("First database: ");
System.out.println("Port is " + firstContainer.getMappedPort(5432));
System.out.println("URL is " + firstContainer.getJdbcUrl());
System.out.println("Second database: ");
System.out.println("Port is " + secondContainer.getMappedPort(5432));
System.out.println("URL is " + secondContainer.getJdbcUrl());
System.out.println("Databases ready");
}
private void updateDataSourceProps(String name, PostgreSQLContainer container) {
System.setProperty("spring." + name + ".url", container.getJdbcUrl());
System.setProperty("spring." + name + ".username", container.getUsername());
System.setProperty("spring." + name + ".password", container.getPassword());
}
}
And then use the extension in test
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@ExtendWith({StartDatabaseAndCreateSchema.class})
class TestContainerTest extends BaseTestIntegration {
@Autowired private Repository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
}
@Test
@DisplayName("#findAll")
void findAll() {
// Given
repository.saveAll(List.of(<A_RECORD>);
// When
List<RECORD> actual = repository.findAll();)
// Then
assertThat(actual)
.singleElement()
.usingRecursiveComparison()
.isEqualTo(<A_RECORD>);
}
}
Given this configuration
PostgreSQLContainer permissionPostgreSQLContainer =
(PostgreSQLContainer)
new PostgreSQLContainer("postgres:16-alpine")
.withDatabaseName("permission")
.withUsername("user")
.withPassword("password")
.withExposedPorts(5432)
.withReuse(true);
Log the port at runtime
System.out.println("Port is " + permissionPostgreSQLContainer.getMappedPort(5432));
System.out.println("URL is " + permissionPostgreSQLContainer.getJdbcUrl());
Or use docker ps
Then connect
psql postgresql://user:password@localhost:$PORT/trace
<logger name="org.testcontainers" level="INFO"/>
<!-- The following logger can be used for containers logs since 1.18.0 -->
<logger name="tc" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>
<logger name="com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire" level="OFF"/>
https://java.testcontainers.org/supported_docker_environment/logging_config/
Singleton https://java.testcontainers.org/test_framework_integration/manual_lifecycle_control/
With spring boot
https://github.com/bedla/spring-boot-postgres-testcontainers/tree/master