Spring Boot
Testing
Liquibase
Spring Boot Testing
Database Migration

Spring boot testing with liquibase fails

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When Spring Boot tests fail around Liquibase, the failure is usually not Liquibase itself but the relationship between the test database, the migration scripts, and the test application context. In other words, the changelog is being executed in an environment that does not behave like the one your application normally expects.

Understand Why Liquibase Runs During Tests

By default, Spring Boot starts Liquibase when the application context starts. That includes many integration tests, because from Spring’s point of view a test context is still an application context that needs a schema.

This is helpful when you want realistic tests. It becomes frustrating when:

  • the test database differs from production
  • the changelog uses SQL that the test database does not support
  • multiple test classes share and mutate the same schema
  • a slice test loads Liquibase even though the schema is irrelevant

The first step is to decide which kind of test you are writing. Integration tests usually should run migrations. Lightweight unit or slice tests often should not.

Make the Test Database Match the Changelog

One of the most common causes of Liquibase failures is using H2 for tests while the changelog was written with PostgreSQL or MySQL-specific SQL in mind. A migration that passes in production can fail instantly in tests because the database engine is different.

A more reliable pattern is to use the same database engine in tests through Testcontainers:

java
1import org.junit.jupiter.api.Test;
2import org.springframework.boot.test.context.SpringBootTest;
3import org.springframework.test.context.DynamicPropertyRegistry;
4import org.springframework.test.context.DynamicPropertySource;
5import org.testcontainers.containers.PostgreSQLContainer;
6import org.testcontainers.junit.jupiter.Container;
7import org.testcontainers.junit.jupiter.Testcontainers;
8
9@Testcontainers
10@SpringBootTest
11class OrderRepositoryTest {
12
13    @Container
14    static PostgreSQLContainer<?> postgres =
15        new PostgreSQLContainer<>("postgres:16");
16
17    @DynamicPropertySource
18    static void configure(DynamicPropertyRegistry registry) {
19        registry.add("spring.datasource.url", postgres::getJdbcUrl);
20        registry.add("spring.datasource.username", postgres::getUsername);
21        registry.add("spring.datasource.password", postgres::getPassword);
22    }
23
24    @Test
25    void contextLoads() {
26    }
27}

This gives Liquibase the same database dialect your real application expects, which eliminates a large class of "works in prod, fails in test" problems.

Keep the Test Profile Configuration Explicit

If you do use a dedicated test profile, make sure the datasource and Liquibase settings are aligned. A common pattern in application-test.yml is:

yaml
1spring:
2  datasource:
3    url: jdbc:postgresql://localhost:5432/testdb
4    username: test
5    password: test
6  liquibase:
7    change-log: classpath:db/changelog/db.changelog-master.yaml

The important part is consistency. If your test datasource points to one database but Liquibase is configured implicitly against something else, the startup failure can look mysterious even though the root cause is simply configuration drift.

It is also worth checking whether your tests are reusing a dirty database between runs. Liquibase may behave differently when the changelog table already exists or when previous test data has altered the expected schema state.

Disable Liquibase Only for Tests That Do Not Need It

Not every test should pay the price of full schema migration. If you are running a controller slice test or a unit-style Spring test that does not touch the database, disable Liquibase for that test scope:

java
1import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
2import org.springframework.test.context.TestPropertySource;
3
4@WebMvcTest
5@TestPropertySource(properties = "spring.liquibase.enabled=false")
6class OrderControllerTest {
7}

This is a good fix when the test is not supposed to care about persistence at all.

What you should avoid is disabling Liquibase globally just to silence failures in integration tests. That gives you fast green tests and a false sense of safety, because the schema used in production is no longer being validated in the test environment.

Common Pitfalls

The biggest mistake is testing against H2 while the changelog contains production-database-specific SQL. That mismatch causes many avoidable Liquibase failures.

Another issue is using one shared test database across many tests without controlling isolation. A schema altered by one test run can affect the next one in surprising ways.

Developers also sometimes disable Liquibase for all tests instead of deciding which tests actually need migrations. That removes useful coverage from integration tests.

Finally, when a migration fails, read the original SQL or dialect error carefully. The top-level Spring Boot startup failure often hides the real database-specific reason several causes down the stack trace.

Summary

  • Liquibase runs during many Spring Boot tests because the test context starts like a real application context.
  • Failures are often caused by database-dialect mismatches, especially when using H2 against production-specific changelogs.
  • Testcontainers is often the most reliable fix for integration tests because it matches the real database engine.
  • Keep test datasource and Liquibase configuration explicit and consistent.
  • Disable Liquibase only for tests that genuinely do not need schema migrations.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.