I am setting up a microservice using Java Spring Boot. The application runs in a Docker container and connects to a MySQL database on the local machine. For testing purposes, I want to avoid the overhead of MySQL and use an H2 in-memory database for unit tests instead.
The following files are part of the configuration: build.gradle, application.properties, application-test.properties, and Dockerfile
Despite following some online guides, I am encountering build errors:
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@44976b08 testClass = com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplicationTests, locations = [], classes = [com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@5023bb8b, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@604f2bd2, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@30b19518, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@62e20a76, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@28e8dde3, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@59252cb6, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@cc9a5aaa], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180)
<snip>
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802)
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
<snip>
at app//org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108)
at app//org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225)
at app//org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152)
... 19 more
Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:276)
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
<snip>
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1849)
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798)
... 39 more
Caused by: org.hibernate.boot.registry.selector.spi.StrategySelectionException: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.selectStrategyImplementor(StrategySelectorImpl.java:154)
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.resolveStrategy(StrategySelectorImpl.java:236)
<snip>
at app//org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
... 54 more
Caused by: org.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [org.hibernate.dialect.MySQL5Dialect]
at app//org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:126)
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.selectStrategyImplementor(StrategySelectorImpl.java:150)
... 64 more
Caused by: java.lang.ClassNotFoundException: Could not load requested class : org.hibernate.dialect.MySQL5Dialect
at org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader.findClass(AggregatedClassLoader.java:216)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:123)
... 65 more
The repository with my current setup can be found here:
GitHub Repository: [apptraqer (ehc/ArchitectureOverview branch)]1
What is the best way to configure Spring Boot to use H2 for unit tests, while keeping MySQL as the runtime configuration? I’d appreciate guidance on how to adjust the mentioned files and resolve these errors.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.6'
id 'application'
}
application {
mainClass = 'com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication'
}
bootJar {
mainClass = 'com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication'
}
group = 'com.apptraqer'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
// Spring Boot Starters
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
// OAuth2 and Security Enhancements
implementation 'org.springframework.security:spring-security-oauth2-jose'
// JSON Web Token (JJWT)
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-orgjson:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
// Database Connector
runtimeOnly 'mysql:mysql-connector-java:8.0.26'
// Testing Dependencies
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'javax.persistence:javax.persistence-api:2.2'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
It is my impression I need to have two application.properties, one for the Docker container and one for the test:
application.properties
# application.properties (Common Settings for All Environments)
# Application Name and Port (could be overridden per environment if needed)
spring.application.name=apptraqer-auth-service
server.port=8081
# Database Configuration (default to MySQL for non-test environments)
spring.datasource.url=jdbc:mysql://apptraqer-mysql:3306/apptraqerdata
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate/JPA Settings (should be the same across all environments)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
application-test.properties
# application-test.properties (Test Environment Overrides)
# Override Database Configuration for Test with H2 (or whatever you use in tests)
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
#H2 settings
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
Dockerfile
FROM openjdk:17-jdk-slim as build
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY gradlew ./
COPY gradle/wrapper/ ./gradle/wrapper/
ENV SPRING_PROFILES_ACTIVE=test
RUN ./gradlew build --no-daemon
COPY src ./src
RUN ./gradlew build
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/build/libs/apptraqer-auth-service.jar apptraqer-auth-service.jar
EXPOSE 8081
CMD ["java", "-jar", "apptraqer-auth-service.jar"]
1
Here are the changes I had to do for making the application to build properly.
For simple and straight forward configuration of H2 for the purpose of running unit tests we can use the @AutoConfigureTestDatabase as explained at the end of the Auto-configured Data JPA Tests section in the spring boot documentation. This will configure a test datasource based on what we add in the classpath which is H2 in our case.
Here is a sample of how we can add the annotation in the code
@SpringBootTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class UserTest{}
Do note that the above is needed for only replacing the Datasource bean we are creating in the application. However say by moving the datasource properties to a separate config file each for tests and application like having the MySQL datasource configs in a file called application-docker.yaml and not providing any datasource configs in any other yaml file. This way when our datasource is autoconfigured it does not always know the MySQL configs (unless profile docker is active). In this setup spring boot can detect H2 on the classpath and will configure H2 automatically for us. And when deploying we can run the application in docker with the profile docker active to instruct spring boot to connect to MySQL. Similarly when running unit tests we do not activate the profile docker and spring boot will configure H2 then.
Some other corrections I was able to find are.
- Spring boot 3.4 no longer depends on javax JPA APIs
this dependency must be removedimplementation 'javax.persistence:javax.persistence-api:2.2'
and the JPA imports must be from jakarta package which is pulled transitively by spring boot starter jpa - Since we are configuring MySQL dialect in application.properties and H2 dialect in application-test.properties for spring boot to use application-test.properties config we need to activate the test profile when running unit tests. One simple way I know is by adding the @ActiveProfiles annotation above the test class like this
@ActiveProfiles(profiles = {"test"})
Hope this helps.
In Gradle you can put
tasks.named('test') {
useJUnitPlatform()
systemProperty("spring.profiles.active", "test")
}
to set your Spring Profile to test
for this task.
TobiMoss is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.