Spring Boot DataJdbcTest with PostgreSQL and Liquibase
Tags: springboot, liquibase, postgressql, testcontainers
Published: 2024-02-28 04:29:13

In this article I wanted to share a little journey I took creating a Spring Boot unit test that exercises a [Spring Data JDBC repository](https://docs.spring.io/spring-data/relational/reference/repositories/definition.html) running against a [Postgresql](https://www.postgresql.org/) database [initialized with Liquibase](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.liquibase). I made a couple of mistakes along the way that I wanted to document here and provide the solutions for those.

In the [Spring Initializr](https://start.spring.io) I chose the dependencies:

- Spring Data JDBC
- Liquibase Migration
- PostgreSQL Driver

To initialize the schema, I created the Liquibase changelog file at `src/main/resources/db/changelog/db.changelog-master.yaml` where I create a `user` table:

```yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: gdb
      changes:
        - createTable:
            tableName: user
            columns:
              - column:
                  name: id
                  autoIncrement: true
                  type: BIGINT
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: identifier
                  type: VARCHAR(50)
                  constraints:
                    nullable: false
              - column:
                  name: issuer
                  type: VARCHAR(100)
                  constraints:
                    nullable: true
```

The `User` entity record/class is declared as

```java
import org.springframework.data.annotation.Id;

public record User(
    @Id
    Long id,
    String identifier,
    String issuer
) {
}
```

And finally, the CRUD repository interface:

```java
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {
}
```

At first I started with the test class written as

```java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@DataJdbcTest
class UserRepositoryTest {
    @Container
    static PostgreSQLContainer<?> pg = 
        new PostgreSQLContainer<>("postgres:16");

    @Autowired
    UserRepository userRepository;

    @Test
    void saveUser() {
        final User result = userRepository.save(
            new User(null, "name1", "issuer1")
        );

        assertThat(result.id()).isNotNull();
    }
}
```

but that resulted in the error

```
Error creating bean with name 'liquibase' defined in class path resource
[org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: 
Failed to instantiate [liquibase.integration.spring.SpringLiquibase]: 
Factory method 'liquibase' threw exception with message: 
Error creating bean with name 'dataSource': 
Failed to replace DataSource with an embedded database for tests. 
If you want an embedded database please put a supported one on the classpath 
or tune the replace attribute of @AutoConfigureTestDatabase.
```

It was one of those errors that actually tells you the solution. Nice! So, I added:

```java
@Testcontainers
@AutoConfigureTestDatabase(
    replace = AutoConfigureTestDatabase.Replace.NONE
)
@DataJdbcTest
class UserRepositoryTest {
```

The next error was

```
Error creating bean with name 'dataSource' defined in class path resource 
[org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: 
Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: 
Factory method 'dataSource' threw exception with message: 
Failed to determine a suitable driver class
```

I remembered that Spring Boot added the [service connections feature](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.testcontainers.service-connections), so added that to the container field's declaration:

```java
    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> pg = 
        new PostgreSQLContainer<>("postgres:16");
```

Now the test passes!