I have a SpringBoot app and I have added JUnit5 integration tests that use testcontainers:
class ControllerClassTest extends AbstractIntegrationTest {
@ParameterizedTest(name = "{0}")
@CsvSource({
// ... test args ...
})
@DisplayName("Happy flow")
void testEndpoint(String... args) {
// using rest assured
}
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MainApplication.class)
public abstract class AbstractIntegrationTest {
private static final GenericContainer<?> POSTGRES_INSTANCE = new GenericContainer<>(
new ImageFromDockerfile("my-db-image", false)
.withDockerfile(Path.of("docker/my-db-setup.Dockerfile")))
.withCreateContainerCmdModifier(cmd -> {
cmd.withName("my-db-container");
Objects.requireNonNull(cmd.getHostConfig())
.withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
});
private SqlScriptUtil scriptUtils;
@Autowired
@Qualifier("appDataSource")
private DataSource dataSource;
@PostConstruct
void setupDataSource() {
scriptUtils = new SqlScriptUtil(dataSource);
}
@BeforeAll
static void init() {
POSTGRES_INSTANCE.start();
}
/**
* Helper method to setup a DB state
*/
protected void setupDatabaseState(String dbBackup) {
scriptUtils.executeScript("db/state/" + dbBackup + ".sql");
}
// others ...
}
I need to decouple from the docker dependency for the team's local and the server development environment.
It is worth noting I have DB migration scripts using Liquibase and two changeset files that get executed in order - first one uses the admin DB user to create the DB schema and a custom user. Second one utilises the new user and creates the DB objects and other migration updates.
I tried going for an approach that utilises spring profiles and using embedded Postgres DB from io.zonky.test. That way I can keep the existing utilisation of test-containers and I can execute the tests quicker and other team members can execute them without having docker engine installed on their machine, but we can also execute them in a pipeline executor so that tests are more accurate and are more similar with actual running environment. Is this a correct approach? Am I making my life unnecessary harder?
What I have done from the above state is I have removed the testcontainer instance from the abstract test and created profile conditional test configurations for the database. However the tests fail as they cannot connect to a DB instance. For some reason the configuration beans do not get instantiated thus there is nothing to listen at the specific port:
@ActiveProfiles("test")
@Import({TestPostgresInstance.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MainApplication.class)
public abstract class AbstractIntegrationTest {
private SqlScriptUtil scriptUtils;
@Autowired
@Qualifier("appDataSource")
private DataSource dataSource;
@PostConstruct
void setupDataSource() {
scriptUtils = new SqlScriptUtil(dataSource);
}
/**
* Helper method to setup a DB state
*/
protected void setupDatabaseState(String dbBackup) {
scriptUtils.executeScript("db/state/" + dbBackup + ".sql");
}
// others ...
}
public interface TestPostgresInstance {
int DB_PORT = 5432;
}
@TestConfiguration
@Profile("test & (local | dev)")
@AutoConfigureEmbeddedDatabase(
provider = ZONKY,
type = POSTGRES)
public class EmbeddedPostgresDb implements TestPostgresInstance {
}
@TestConfiguration
@Profile("test & !local & !dev")
public class DockerPostgresDb implements TestPostgresInstance {
public DockerPostgresDb() {
GenericContainer<?> dbInstance = GenericContainer<>(
new ImageFromDockerfile("my-db-image", false)
.withDockerfile(Path.of("docker/my-db-setup.Dockerfile")))
.withCreateContainerCmdModifier(cmd -> {
cmd.withName("my-db-container");
Objects.requireNonNull(cmd.getHostConfig())
.withPortBindings(new PortBinding(Ports.Binding.bindPort(5432), new ExposedPort(5432)));
});
dbInstance.start();
}
}