Search code examples
spring-bootkotlintransactionsspring-data-mongodb

Spring Data MongoDB can't use @Transactional of my Repository Test: Failed to retrieve PlatformTransactionManager for @Transactional test


I am starting a simple spring web service that uses Spring Data MongoDB. I am used to use Spring Data with MySQL and here using @Transactional in my test classes works fine.

However in this case I receive the following error message:

Failed to retrieve PlatformTransactionManager for @Transactional test: [DefaultTestContext@39277750 testClass = ItemRepositoryTest, testInstance = com.ruckpack.equipmentservice.repository.ItemRepositoryTest@73bbbc73, testMethod = should test@ItemRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@2c217989 testClass = ItemRepositoryTest, locations = '{}', classes = '{class com.ruckpack.equipmentservice.EquipmentServiceApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@15112c86, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3400f6a3, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@39669485, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@dfa43ef, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@103a4d41, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@34fe2a08], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
java.lang.IllegalStateException: Failed to retrieve PlatformTransactionManager for @Transactional test: [DefaultTestContext@39277750 testClass = ItemRepositoryTest, testInstance = com.ruckpack.equipmentservice.repository.ItemRepositoryTest@73bbbc73, testMethod = should test@ItemRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@2c217989 testClass = ItemRepositoryTest, locations = '{}', classes = '{class com.ruckpack.equipmentservice.EquipmentServiceApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@15112c86, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3400f6a3, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@39669485, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@dfa43ef, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@103a4d41, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@34fe2a08], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplication

Now this is my test class

@SpringBootTest
@Transactional
internal class ItemRepositoryTest @Autowired constructor(
    private val itemRepository: ItemRepository
) {
    @Test
    fun `should test`() {
        val expected = Item("001", "Meindl", "SuperBoot 300")
        itemRepository.save(expected)
        val actual = itemRepository.findById(expected.id)

        assertThat(actual).isPresent
        assertThat(actual.get()).isEqualTo(expected)
    }
}
@Repository
interface ItemRepository: MongoRepository<Item, String>

Any my application class looks like this:

@SpringBootApplication
class EquipmentServiceApplication

fun main(args: Array<String>) {
    runApplication<EquipmentServiceApplication>(*args)
}

I saw this tutorial: https://www.baeldung.com/spring-data-mongodb-transactions where the usage was done without any special configuration. That is why I am confused it does not work.

Here is my build.gradle.kt for completeness

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.4.2"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.4.30"
    kotlin("plugin.spring") version "1.4.30"
}

group = "com.ruckpack"
version = "0.0.1"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
    maven { url = uri("https://repo.spring.io/milestone") }
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("de.flapdoodle.embed:de.flapdoodle.embed.mongo")
    testImplementation("org.springframework.security:spring-security-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Solution

  • MongoDB versions 4 and above supports transactions. But are disabled by default in Spring Data MongoDB, you should define MongoTransactionManager explicitly. Please read: https://docs.spring.io/spring-data/mongodb/docs/2.1.0.RC2/reference/html/#mongo.transactions And https://www.baeldung.com/spring-data-mongodb-transactions also says "Note that we need to register MongoTransactionManager in our configuration to enable native MongoDB transactions as they are disabled by default."

    Not sure about the syntax in Kotlin, for Java is:

    @Bean
    MongoTransactionManager txManager(MongoDbFactory mongoDbFactory) {
        return new MongoTransactionManager(mongoDbFactory);
    }