Last active
March 27, 2025 10:59
-
-
Save othmane-kinane-nw/cc4f8a325343243e1f0cdbbd6018f85c to your computer and use it in GitHub Desktop.
JUnit tests to ensure there's no missing Liquibase migration and that hibernate successfully valide the final schema
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import liquibase.CatalogAndSchema; | |
import liquibase.Contexts; | |
import liquibase.Liquibase; | |
import liquibase.database.Database; | |
import liquibase.database.DatabaseFactory; | |
import liquibase.database.jvm.JdbcConnection; | |
import liquibase.diff.output.DiffOutputControl; | |
import liquibase.exception.DatabaseException; | |
import liquibase.exception.LiquibaseException; | |
import liquibase.integration.commandline.CommandLineUtils; | |
import liquibase.resource.ClassLoaderResourceAccessor; | |
import liquibase.resource.ResourceAccessor; | |
import org.hibernate.SessionFactory; | |
import org.junit.jupiter.api.Test; | |
import org.junit.jupiter.api.io.TempDir; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | |
import org.springframework.test.annotation.DirtiesContext; | |
import org.springframework.test.context.DynamicPropertyRegistry; | |
import org.springframework.test.context.DynamicPropertySource; | |
import org.testcontainers.containers.JdbcDatabaseContainer; | |
import org.testcontainers.containers.PostgreSQLContainer; | |
import org.testcontainers.junit.jupiter.Container; | |
import org.testcontainers.junit.jupiter.Testcontainers; | |
import javax.sql.DataSource; | |
import javax.xml.parsers.ParserConfigurationException; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.sql.Connection; | |
import java.util.Optional; | |
import static org.junit.jupiter.api.Assertions.*; | |
// Tested with these dependencies: | |
// - Spring Boot: 3.4.4 | |
// - Spring Framework: 6.2.5 | |
// - Spring Data JPA: 3.4.4 | |
// - Liquibase Core & Hibernate6 Extension: 4.31.1 | |
// - Hibernate Core & Envers: 6.6.11.Final | |
// - PostgreSQL JDBC Driver: 42.7.5 | |
// - Testcontainers (JUnit Jupiter, PostgreSQL, JDBC): 1.20.6 | |
@Testcontainers | |
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) | |
@DataJpaTest | |
class LiquibaseMigrationIntegrationTests { | |
@Container | |
private static final JdbcDatabaseContainer<?> testDatabase = | |
new PostgreSQLContainer<>("postgres:17.4"); | |
private static final ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(); | |
private static final String changeLogFile = "db/changelog-master.yaml"; | |
private static final String hibernateReferenceUrl = | |
"hibernate:spring:" + BASE_PACKAGE_NAME + "?dialect=org.hibernate.dialect.PostgreSQLDialect"; | |
@Autowired | |
private DataSource dataSource; | |
@TempDir | |
private Path tempDir; | |
@Autowired | |
private SessionFactory sessionFactory; | |
@Test | |
void liquibase_generate_non_empty_diff_against_fresh_database() throws Exception { | |
Optional<String> diffAsYamlOptional; | |
try (Connection connection = dataSource.getConnection()) { | |
Database targetDatabase = getDatabase(connection); | |
Database referenceDatabase = getHibernateReferenceDatabase(); | |
diffAsYamlOptional = getDiffAsYaml(referenceDatabase, targetDatabase); | |
} | |
// Be aware that on older versions of liquibase generate a diff file containing | |
// an empty array even if no diff were detected. Please adjust if it's your case. | |
assertTrue(diffAsYamlOptional.isPresent(), "diff on fresh database is expected to have content, but it was empty."); | |
} | |
@Test | |
void liquibase_generate_empty_diff_after_applying_all_migrations() throws Exception { | |
Optional<String> diffAsYamlOptional; | |
try (Connection connection = dataSource.getConnection()) { | |
Database targetDatabase = getDatabase(connection); | |
applyMigrations(targetDatabase); | |
Database referenceDatabase = getHibernateReferenceDatabase(); | |
diffAsYamlOptional = getDiffAsYaml(referenceDatabase, targetDatabase); | |
} | |
// Be aware that older versions of liquibase generate a diff file containing | |
// an empty array even if no diff were detected. Please adjust if it's your case. | |
assertTrue(diffAsYamlOptional.isEmpty(), | |
() -> "diff after applying all migrations is expected to be empty, but found this instead:\n\n" + diffAsYamlOptional.orElseThrow()); | |
} | |
@Test | |
void hibernate_successfully_validate_schema_created_by_liquibase() throws Exception { | |
try (Connection connection = dataSource.getConnection()) { | |
Database targetDatabase = getDatabase(connection); | |
applyMigrations(targetDatabase); | |
} | |
assertDoesNotThrow(() -> sessionFactory.getSchemaManager().validateMappedObjects(), | |
"hibernate schema validation failed after applying all migrations"); | |
} | |
@DynamicPropertySource | |
private static void registerDatasourceProperties(DynamicPropertyRegistry registry) { | |
if (testDatabase.isCreated()) { | |
testDatabase.close(); | |
} | |
testDatabase.start(); | |
registry.add("spring.datasource.url", testDatabase::getJdbcUrl); | |
registry.add("spring.datasource.username", testDatabase::getUsername); | |
registry.add("spring.datasource.password", testDatabase::getPassword); | |
registry.add("spring.liquibase.enabled", () -> "false"); | |
registry.add("spring.jpa.hibernate.ddl-auto", () -> "none"); | |
} | |
private static Database getHibernateReferenceDatabase() throws DatabaseException { | |
return DatabaseFactory.getInstance().openDatabase( | |
hibernateReferenceUrl, | |
null, null, null, resourceAccessor); | |
} | |
private static Database getDatabase(Connection connection) throws DatabaseException { | |
return DatabaseFactory.getInstance() | |
.findCorrectDatabaseImplementation(new JdbcConnection(connection)); | |
} | |
private static void applyMigrations(Database targetDatabase) throws LiquibaseException { | |
Liquibase liquibase = new Liquibase(changeLogFile, resourceAccessor, targetDatabase); | |
liquibase.update(new Contexts()); | |
} | |
private Optional<String> getDiffAsYaml(Database referenceDatabase, Database targetDatabase) throws LiquibaseException, IOException, ParserConfigurationException { | |
Path diffFile = tempDir.resolve("diff.yaml"); | |
DiffOutputControl diffOutputControl = new DiffOutputControl(false, false, false, null).addIncludedSchema(new CatalogAndSchema(null, null)); | |
CommandLineUtils.doDiffToChangeLog(diffFile.toAbsolutePath().toString(), referenceDatabase, targetDatabase, null, diffOutputControl, | |
null, null, null, "none", "none"); | |
if (Files.notExists(diffFile)) { | |
return Optional.empty(); | |
} | |
return Optional.of(Files.readString(diffFile)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment