So, I've been trying to start a migration from Maven to Gradle at my work but I've now run into a serious problem which I can't seem to wrap my head around.
I basically just wanna run some simple liquibase migrations for my tests, for which I spin up two testcontainers. One for a rabbitmqexchange and one for a postgres DB.
I've set up the postgres container using a little workaround described here: Testing Spring Boot Applications with Kotlin and Testcontainers
I've tried it a thousand ways and scoured all related questions but I can't seem to figure out what the problem is...
Here is the setup using Gradle 6.7.1:
build.gradle.kts
plugins {
id("org.springframework.boot") version "2.4.0"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
application
kotlin("jvm") version "1.4.20"
id("org.jetbrains.kotlin.plugin.spring") version "1.4.20"
id("org.liquibase.gradle") version "2.0.4"
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.cloud:spring-cloud-stream:3.0.9.RELEASE")
implementation("org.springframework.cloud:spring-cloud-stream-binder-rabbit:3.0.9.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.liquibase:liquibase-core:4.0.0")
// liquibaseRuntime("org.liquibase:liquibase-core:4.0.0")
runtimeOnly("org.postgresql:postgresql:42.2.18")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
testImplementation("org.testcontainers:testcontainers:1.15.0")
testImplementation("org.testcontainers:junit-jupiter:1.15.0")
testImplementation("org.testcontainers:rabbitmq:1.15.0")
testImplementation("org.testcontainers:postgresql:1.15.0")
}
description = "my-project"
java.sourceCompatibility = JavaVersion.VERSION_1_8
application {
mainClass.set("path/to/the/main/class")
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
tasks.register<Copy>("copyJarToTargetDir") {
mustRunAfter("build")
val outputJar = "$buildDir/libs"
val targetDir = "$projectDir/target"
from(outputJar)
include("*.jar")
into(targetDir)
}
tasks.register<Delete>("deleteTargetDir") {
mustRunAfter("clean")
delete("$projectDir/target")
}
tasks.named("build") {
finalizedBy("copyJarToTargetDir")
}
tasks.named("clean") {
finalizedBy("deleteTargetDir")
}
The Base test:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@Testcontainers
class PartnerEdgeServiceApplicationBaseTests {
@Autowired
lateinit var vehicleRepository: VehicleRepository
@Autowired
lateinit var vehicleStreamChannel: VehicleDataChannel
@LocalServerPort
private val port: Int = 0
@Test
fun `check application`() {
println("Howdy, Partners!")
}
}
And the test in question:
class MessageSinkTest : ApplicationBaseTests() {
companion object {
@Container
val rabbitMQContainer = RabbitMQContainer("rabbitmq:alpine")
@Container
val postgresContainer: KPostgreSQLContainer = KPostgreSQLContainer("postgres:10.11-alpine")
@JvmStatic
@DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", postgresContainer::getJdbcUrl)
registry.add("spring.datasource.password", postgresContainer::getPassword)
registry.add("spring.datasource.username", postgresContainer::getUsername)
registry.add("spring.rabbitmq.addresses", rabbitMQContainer::getAmqpUrl)
registry.add("spring.rabbitmq.username", rabbitMQContainer::getAdminUsername)
registry.add("spring.rabbitmq.password", rabbitMQContainer::getAdminPassword)
registry.add("spring.liquibase.url", postgresContainer::getJdbcUrl)
registry.add("spring.liquibase.user", postgresContainer::getUsername)
registry.add("spring.liquibase.password", postgresContainer::getPassword)
}
}
@Test
@DisplayName("Should extract the vehicle data and persist in the DB")
fun test1() {
val id = "id"
val locationId = 1L
val numberPlate = "Some plate"
val buildSeries = "VW_TIGUAN"
val fuel = 75
val fuelType = "GASOLINE"
vehicleStreamChannel.input().send(
GenericMessage(
VehicleDataStreamDto(
id = id,
locationId = locationId,
numberPlate = numberPlate,
buildSeries = buildSeries,
fuel = fuel,
fuelType = fuelType
)
)
)
val persistedVehicle = vehicleRepository.findByIdOrNull(vin)
assertEquals(id, persistedVehicle?.id)
}
}
And the application.yml:
server:
servlet:
context-path: /my-project
streaming:
retries: 5
concurrency: 15
amqpGroupName: my_project
spring:
jpa:
hibernate:
ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQL94Dialect
liquibase:
url: ${spring.datasoure.url}
user: ${spring.datasource.username}
password: ${spring.datasoure.password}
contexts: environment-name-will-be-placed-here
change-log: classpath:/db/changelog/db.changelog-master.xml
datasource:
driver-class-name: org.postgresql.Driver
cloud:
stream:
rabbit:
bindings:
vehiclestream:
consumer:
declareExchange: true
---
spring:
jpa:
database-platform: org.hibernate.dialect.PostgreSQL94Dialect
hibernate:
ddl-auto: none
config:
activate:
on-profile: local
When I run the test I get the following logs:
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:99)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:350)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:355)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$7(ClassBasedTestDescriptor.java:350)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:313)
at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743)
at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:742)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:349)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$4(ClassBasedTestDescriptor.java:270)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:269)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
at java.util.Optional.orElseGet(Optional.java:267)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
at java.util.ArrayList.forEach(ArrayList.java:1259)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.util.ArrayList.forEach(ArrayList.java:1259)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:133)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.ChangeLogParseException: The file classpath:/db/changelog/db.changelog-master.xml was not found in
- a bunch of paths
my db.changelog-master.xml is at the following location (from the project's root):
src/main/resources/db/changelog/db.changelog-master.xml
Hence, the error does not really make sense or I'm missing something (which is obviously the case...). Anyways, any kind of help is much appreciated!!
UPDATE:
so, I did make a little change. I swapped out these two dependencies implementation("org.liquibase:liquibase-core:4.0.0")
for liquibaseRuntime("org.liquibase:liquibase-core:4.0.0")
and removed the jpa.properties
on my application.yml. I did get a different error:
{"@timestamp":"2020-11-29T09:05:08.612+01:00","@version":"1","logmessage":"SQL Error: 0, SQLState: 42P01","logger_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","thread_name":"Test worker","level":"WARN","level_value":30000,"vin":"vin"}
{"@timestamp":"2020-11-29T09:05:08.614+01:00","@version":"1","logmessage":"ERROR: relation \"vehicles\" does not exist\n Position: 290","logger_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","thread_name":"Test worker","level":"ERROR","level_value":40000,"vin":"vin"}
{"@timestamp":"2020-11-29T09:05:08.617+01:00","@version":"1","logmessage":"HHH000327: Error performing load command","logger_name":"org.hibernate.event.internal.DefaultLoadEventListener","thread_name":"Test worker","level":"INFO","level_value":20000,"stack_trace":"org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Exception thrown while invoking VehicleDataSink#processData[1 args]; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a];
So, for future reference: The problem was not anything gradle/liquibase specific. It was simply the fact, that I created the directories db/changelog
in one step in Intellij, which created one dir db.changelog
instead of /changelog
being the child of db
. After splitting that up in two separate steps, everything is working out fine!