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(); } }