I have a Java Spring boot project, heavily using the database (Postgres) for it's repository/data. It is the basic MVC project, controllers are all REST controllers. The project works well (service is up, able to call service via REST clients and all).
Now, I am adding the unit tests to it. I am pretty new to Spring boot and mostly the unit test part. Owing to the CI/CD (build pipeline), I cannot use the persistent/external DB for tests. Hence I need to use in-memory DB.
The initial run (Main class) runs a bunch of DB queries to build up cache while project comes up. So I would need postgres DB for testing (lots of DB functions used).
Basically, I would need to use Testcontainers (postgresql). I am writing a very basic test first to get hold of it.
I have the schema.sql and data.sql stored (to be used only for testing).
src
|
main
test
|
resources
|
application-test.properties
schema.sql
data.sql
Relevant pom.xml
<properties>
<java.version>11</java.version>
<testcontainers.version>1.15.1</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20201115</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
My Test class:
@Testcontainers
@Sql(scripts = {"file:src/test/resources/schema.sql","file:src/test/resources/data.sql"})
class ApplicationTests {
@Container
static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:12")
.withUsername("testcontainers")
.withPassword("testcontainers")
.withDatabaseName("tescontainers");
@Test
void testPostgreSQLModule() throws SQLException {
try (Connection connection = DriverManager
.getConnection(postgreSQLContainer.getJdbcUrl(), "testcontainers", "testcontainers");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM table_from_schema")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
System.out.println(resultSet.getString("column1"));
}
}
}
}
}
I am simply trying to test the DB.
However, when I run the test, it fails saying that
org.postgresql.util.PSQLException: ERROR: relation "table_from_schema" does not exist
I tried to debug it, i.e. stopped just inside my Test (testPostgreSQLModule). I can see the docker component with Postgres.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fc3ff1e04ceb postgres:12 "docker-entrypoint.s…" 16 seconds ago Up 15 seconds
But when I login to it and run psql, I see the DB (tescontainers) is created, however it does not have any schema (tables/functions).
tescontainers=# \dt
Did not find any relations.
tescontainers=#
Basically, my files are not being run.
Does @SQL annotation on class level does not work with Testcontainers initialization as in my case?
What is needed here so both my initial scripts run?
I tried using .withInitScript, and it runs. However, I have lots of data to initialize and the file is too large (and would grow), so I separate DDL (schema) and Inserts (data). Now, my issue is how to run multiple init files (schema.sql, data.sql) using "withInitScript" ? So I tries @SQL annotation, but it does not seems to work.
---UPDATE/EDIT----
To give the context clear, I am looking for below. If anyone can guide please?
- All profile (dev/ist/uat/prod) should be using their respective persistence DB (from their application.env.properties).
- Only for the test, I need the in-memory DB, but cannot use H2 (and similar) as I have lots of DB related tests and need Postgres (functions , etc). So, trying out Testcontainers.
- When the application boots up, it fetches some data from respective DB (based on env) to prepare the initial cache and other methods will use it while servicing any rest calls. Hence, for Test (only) I need a new in-memory DB with all schema/data (which I can provide via SQL files) , so that while test, the boot up should use that test DB and corresponding tests wil work based on that initial data.
- So I need a way to bring up test DB (in-memory/testcontainers) whenever Test runs, and pass multiple SQL files for initialization of Test DB (before any test runs). Any idea on what is the best approach?