Hi,
I am trying to setup my project to use MySql testcontainer as part of the gradle build process. Here is what I am trying to achieve when the gradle build will trigger.
- Start the MySql testcontainer as a Gradle Shared Service
- Apply DB migrations using liquibase against testcontianer MySql DB
- Generate Jooq code against testcontianer MySql DB
- Compile the code.
- Run the unit tests
- Run the integrationTest task against testcontianer MySql DB
- Stop the MySql testcontainer. (It will be managed by the Gradle Shared Service)
When I did the setup, it worked for me but after a day or so, I am seeing the below error when I am running ./gradlew clean build
in my local (Mac OS Sonoma 14.5)
Error
Can't instantiate a strategy from org.testcontainers.dockerclient.UnixSocketClientProviderStrategy (ClassNotFoundException). This probably means that cached configuration refers to a client provider class that is not available in this version of Testcontainers. Other strategies will be tried instead.
Could not find a valid Docker environment. Please check configuration. Attempted configurations were:
As no valid configuration was found, execution cannot continue.
See https://java.testcontainers.org/on_failure.html for more details.
* What went wrong:
Execution failed for task ':startMySQLContainer'.
> Failed to create service 'mysqlContainerService'.
> Could not create an instance of type MySQLContainerService.
> Could not find a valid Docker environment. Please see logs and check configuration
Here is my gradle code where I am trying to start the container.
import org.testcontainers.containers.MySQLContainer;
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
buildscript {
repositories {
maven {
name repoName
url repoUrl
credentials() {
username = repoUsername
password = repoPassword
}
}
}
dependencies {
classpath "org.testcontainers:testcontainers:${dependencyManagement.importedProperties['testcontainers.version']}"
classpath "org.testcontainers:mysql:${dependencyManagement.importedProperties['testcontainers.version']}"
classpath "com.mysql:mysql-connector-j:${dependencyManagement.importedProperties['mysql.version']}"
}
}
// Here we register service for providing our database during the build.
gradle.sharedServices.registerIfAbsent('mysqlContainerService', MySQLContainerService) { spec ->
spec.parameters.databaseSchemaName.set(project.ext.databaseSchemaName)
}
/**
* Build service for providing database container.
*/
abstract class MySQLContainerService implements BuildService<MySQLContainerService.Params>, AutoCloseable {
interface Params extends BuildServiceParameters {
Property<String> getDatabaseSchemaName()
}
private final MySQLContainer mysqlContainer;
@javax.inject.Inject
MySQLContainerService(Params params) {
// Services are initialized lazily, on first request to them, so we start container immediately.
long startTime = System.currentTimeMillis()
println("DB Schema Name: ${params.getDatabaseSchemaName().get()}")
mysqlContainer = new MySQLContainer(MySQLContainer.IMAGE)
.withDatabaseName(params.getDatabaseSchemaName().get())
mysqlContainer.start()
long duration = System.currentTimeMillis() - startTime
println("MySQL Testcontainer Started successfully with URL: ${mysqlContainer.getJdbcUrl()}. Total time taken: ${duration} ms")
}
String getJdbcUrl() {
return mysqlContainer.getJdbcUrl()
}
String getUsername() {
return mysqlContainer.getUsername()
}
String getPassword() {
return mysqlContainer.getPassword()
}
String getDriverClassName() {
return mysqlContainer.getDriverClassName()
}
@Override
void close() {
// Ensure to stop container in the end
if (mysqlContainer != null) {
try {
mysqlContainer.stop()
println("MySQL container stopped successfully.")
} catch (Exception e) {
println("Failed to stop MySQL container: ${e.message}")
}
} else {
println("MySQL container is already null (was not started).")
}
}
}
// Ensure the integrationTest uses the same container and stops it after
tasks.named('integrationTest').configure {
doFirst {
def service = gradle.sharedServices.getRegistrations().getByName('mysqlContainerService').service.get()
systemProperty 'spring.datasource.url', service.getJdbcUrl()
systemProperty 'spring.datasource.username', service.getUsername()
systemProperty 'spring.datasource.password', service.getPassword()
systemProperty 'spring.datasource.driver-class-name', service.getDriverClassName()
}
doLast {
systemProperties.remove('spring.datasource.url')
systemProperties.remove('spring.datasource.username')
systemProperties.remove('spring.datasource.password')
systemProperties.remove('spring.datasource.driver-class-name')
}
}
The error is on line mysqlContainer.start()
I have started a discussion with testcontainers
team and as per them it seems like an issue with Gradle Shared services. They have created an issue on their end but wanted me to check with Gradle team about this.
This is what I heard from their team
When using the Gradle Shared Service, the JVM class loader seems to not be able to instantiate the class (which is part of Testcontainers). That seems to be like something to ask Gradle about.
Here is the ticket for testcontainers GH: Could not find a valid Docker environment when using Gradle Shared Build Service (`ClassNotFoundException` when loading strategies) · Issue #9050 · testcontainers/testcontainers-java · GitHub
I am really blocked here, it will be great if someone can help me on this.
Thanks in advance.
- Deba