2

Problem description While system end-to-end tests are invoking methods annotated with @TransactionalEventListener, I'm not able to invoke the same methods in narrower tests annotated with @MicronautTest.

I've tested numerous variants with both injected EntityManager and SessionFactory. @MicronautTest(transactional = false) is also tested. Calling JPA-method inside TestSvcWithTxMethod#someMethod is also tested with same result. I've also tried tests without mocking TestAppEventListener.

The below test/code yields

Verification failed: call 1 of 1: TestAppEventListener(#1).beforeCommit(any())) was not called. java.lang.AssertionError: Verification failed: call 1 of 1: TestAppEventListener(#1).beforeCommit(any())) was not called.

Calls to same mock: 1) TestAppEventListener(#1).hashCode()

Environment: Micronaut 3.7.5, Micronaut Data 3.9.3

Minimal reproducible code Test is failing as well with transactional = false

import io.kotest.core.spec.style.BehaviorSpec
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotest5.MicronautKotest5Extension.getMock
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.DefaultTestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWrapper

@MicronautTest
class AppEventWithBeforeCommitListenerMockTest(
    testSvcWrapper: TestSvcWrapper,
    testAppEventListener: TestAppEventListener
) : BehaviorSpec({

    given("context with app event listener") {
        `when`("calling someMethod") {
            val mockBeforeCommitTestListener = getMock(testAppEventListener)
            every { mockBeforeCommitTestListener.beforeCommit(any()) } answers {}
            every { mockBeforeCommitTestListener.afterRollback(any()) } answers {}

            testSvcWrapper.someMethod(message = "call #1")

            verify { mockBeforeCommitTestListener.beforeCommit(any()) }
        }
    }
}) {
    @MockBean(DefaultTestAppEventListener::class)
    fun mockTestAppEventListener(): TestAppEventListener = mockk()
}

TestSvcWrapper

import jakarta.inject.Singleton

@Singleton
class TestSvcWrapper(
    private val testSvcWithTxMethod: TestSvcWithTxMethod
) {
    fun someMethod(message: String) {
        testSvcWithTxMethod.someMethod(message)
    }
}

TestSvcWithTxMethod

import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional

@Singleton
open class TestSvcWithTxMethod(
    private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    open fun someMethod(message: String) {
        eventPublisher.publishEvent(TestEvent(message))
    }
}

TestEvent

import io.micronaut.core.annotation.Introspected

@Introspected
data class TestEvent(val message: String)

TestAppEventListener

interface TestAppEventListener {
    fun beforeCommit(event: TestEvent)

    fun afterRollback(event: TestEvent)
}

DefaultTestAppEventListener

import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger

@Singleton
open class DefaultTestAppEventListener : TestAppEventListener {

    val receiveCount = AtomicInteger()

    @TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
    override fun beforeCommit(event: TestEvent) {
        receiveCount.getAndIncrement()
    }

    @TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
    override fun afterRollback(event: TestEvent) {
        receiveCount.getAndIncrement()
    }
}
Roar S.
  • 8,103
  • 1
  • 15
  • 37

1 Answers1

0

The answer was found in the micronaut-test repo. Key is to inject SynchronousTransactionManager<Any>, create and then commit/rollback transaction.

I was not able to make mock-test from question pass, most likely because of the annotations, but the following tests are working. I made some modifications to the types in question, hence I added code for the new implementations below.

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.support.DefaultTransactionDefinition
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWithTxMethod

@MicronautTest(transactional = false)
class AppEventWithBeforeCommitListenerTest(
    testSvcWithTxMethod: TestSvcWithTxMethod,
    testAppEventListener: TestAppEventListener,
    transactionManager: SynchronousTransactionManager<Any>
) : BehaviorSpec({

    given("context with app event listener") {

        `when`("calling someMethod with commit") {
            val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
            testSvcWithTxMethod.someMethod(message = "call #1")
            transactionManager.commit(tx)

            then("TestAppEventListener should have received message") {
                testAppEventListener.beforeCommitReceiveCount.get() shouldBe 1
            }
        }

        `when`("calling someMethod with rollback") {
            val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
            testSvcWithTxMethod.someMethod(message = "call #2")
            transactionManager.rollback(tx)

            then("TestAppEventListener should have received message") {
                testAppEventListener.afterRollbackReceiveCount.get() shouldBe 1
            }
        }
    }
})

TestSvcWithTxMethod

import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional

@Singleton
open class TestSvcWithTxMethod(
    private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
    @Transactional
    open fun someMethod(message: String) {
        eventPublisher.publishEvent(TestEvent(message))
    }
}

TestAppEventListener

import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger

@Singleton
open class TestAppEventListener {

    val beforeCommitReceiveCount = AtomicInteger()
    val afterRollbackReceiveCount = AtomicInteger()

    @TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
    open fun beforeCommit(event: TestEvent) {
        beforeCommitReceiveCount.getAndIncrement()
    }

    @TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
    open fun afterRollback(event: TestEvent) {
        afterRollbackReceiveCount.getAndIncrement()
    }
}

TestEvent (unchanged)

import io.micronaut.core.annotation.Introspected

@Introspected
data class TestEvent(val message: String)
Roar S.
  • 8,103
  • 1
  • 15
  • 37