Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlintest does not with spring data jpa #950

Closed
tunaranch opened this issue Aug 20, 2019 · 25 comments · Fixed by #953
Closed

Kotlintest does not with spring data jpa #950

tunaranch opened this issue Aug 20, 2019 · 25 comments · Fixed by #953
Assignees
Labels
bug 🐛 Issues that report a problem or error in the code.

Comments

@tunaranch
Copy link

@DataJpaTest does not appear to work with kotlintest.
The tests fail with the error:

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

Note that the standard JUnit 5 test in the same project works.

See sample project for runnable project

@LeoColman
Copy link
Member

LeoColman commented Aug 20, 2019 via email

@tunaranch
Copy link
Author

I get the same error with 3.4.0:

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   ElementAnnotaionTest.testIt:19 » TransactionRequired No EntityManager with act...
[ERROR]   ElementTest » TransactionRequired No EntityManager with actual transaction ava...
[INFO] 
[ERROR] Tests run: 4, Failures: 0, Errors: 2, Skipped: 0

@LeoColman LeoColman added the bug 🐛 Issues that report a problem or error in the code. label Aug 21, 2019
@LeoColman LeoColman self-assigned this Aug 21, 2019
@LeoColman
Copy link
Member

I'll investigate this

@LeoColman LeoColman mentioned this issue Aug 21, 2019
4 tasks
@fipp
Copy link

fipp commented Aug 22, 2019

Following, I just posted a question about it at stackoverflow, before finding this issue:

https://stackoverflow.com/questions/57607764/autowire-spring-testentitymanager-in-kotlintest-not-working

@LeoColman
Copy link
Member

Ok, this was a very tough one, but I think I managed to do it.

First of all, thanks A LOT for @tunaranch for providing a simple project to test this on. I would have had a hard time to do it myself, as I don't use this kind of tests a lot.

I'll add to this thread what I had to do to solve, because I'm very sure we'll get confused about it in the future.


Solution and explanation

Spring executes transactions (and this include anything inside @DataJpaTest) through a Listener called TransactionalTestExecutionListener. This comes pre-registered with a SpringBootTest, and KotlinTest was executing this listener correctly as per #887.

However, only executing the Listener wasn't enough, as it couldn't autowire the EntityManager. Initially, this was happening because our SpringListener had a fake "test method" that was a pointer to java.lang.Object.hashcode method. We made this hack to allow us to execute code inside Spring Test Framework, and it seemed fine on our tests.

Due to hashcode being a java.lang.Object method, Spring couldn't find the transaction attribute, as it verified directly that the method wasn't from Java Object.

With this in mind, we must use an object from another class. My first attempt was to use a dummy method, declared in the SpringListener itself. But this also wouldn't work, as the listener would never find the TransactionAttribute, as the method doesn't (and shouldn't) declare a @Transaction, and SpringListener also didn't declare that annotation. The only way to have a method with that annotation would be inside user's Spec definition, where the user would choose it.

We don't have methods, as we define everything through a DSL. But that didn't seem an issue, as Spring's Listener would get the Transaction Attribute from the method's class if it doesn't declare it. @DataJPATest already declare transaction attributes, so if we managed to get a method inside it, it would work without having to declare @Transaction.

If we could guarantee that our users were overriding a method, we could get its reference, but we couldn't force that, and referecing a method from Spec wouldn't work (as Spec doesn't declare the attributes).

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

@fipp
Copy link

fipp commented Aug 23, 2019

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

Would it be possible pasting a code snippet of it here, @Kerooker? Great work btw :)

LeoColman added a commit that referenced this issue Aug 23, 2019
More information at #950

Spring executes transactions (and this include anything inside @DataJpaTest) through a Listener called TransactionalTestExecutionListener. This comes pre-registered with a SpringBootTest, and KotlinTest was executing this listener correctly as per #887.

However, only executing the Listener wasn't enough, as it couldn't autowire the EntityManager. Initially, this was happening because our SpringListener had a fake "test method" that was a pointer to java.lang.Object.hashcode method. We made this hack to allow us to execute code inside Spring Test Framework, and it seemed fine on our tests.

Due to hashcode being a java.lang.Object method, Spring couldn't find the transaction attribute, as it verified directly that the method wasn't from Java Object.

With this in mind, we must use an object from another class. My first attempt was to use a dummy method, declared in the SpringListener itself. But this also wouldn't work, as the listener would never find the TransactionAttribute, as the method doesn't (and shouldn't) declare a @transaction, and SpringListener also didn't declare that annotation. The only way to have a method with that annotation would be inside user's Spec definition, where the user would choose it.

We don't have methods, as we define everything through a DSL. But that didn't seem an issue, as Spring's Listener would get the Transaction Attribute from the method's class if it doesn't declare it. @DataJpaTest already declare transaction attributes, so if we managed to get a method inside it, it would work without having to declare @transaction.

If we could guarantee that our users were overriding a method, we could get its reference, but we couldn't force that, and referecing a method from Spec wouldn't work (as Spec doesn't declare the attributes).

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

Fixes #950
LeoColman added a commit that referenced this issue Aug 23, 2019
More information at #950

Spring executes transactions (and this include anything inside @DataJpaTest) through a Listener called TransactionalTestExecutionListener. This comes pre-registered with a SpringBootTest, and KotlinTest was executing this listener correctly as per #887.

However, only executing the Listener wasn't enough, as it couldn't autowire the EntityManager. Initially, this was happening because our SpringListener had a fake "test method" that was a pointer to java.lang.Object.hashcode method. We made this hack to allow us to execute code inside Spring Test Framework, and it seemed fine on our tests.

Due to hashcode being a java.lang.Object method, Spring couldn't find the transaction attribute, as it verified directly that the method wasn't from Java Object.

With this in mind, we must use an object from another class. My first attempt was to use a dummy method, declared in the SpringListener itself. But this also wouldn't work, as the listener would never find the TransactionAttribute, as the method doesn't (and shouldn't) declare a @transaction, and SpringListener also didn't declare that annotation. The only way to have a method with that annotation would be inside user's Spec definition, where the user would choose it.

We don't have methods, as we define everything through a DSL. But that didn't seem an issue, as Spring's Listener would get the Transaction Attribute from the method's class if it doesn't declare it. @DataJpaTest already declare transaction attributes, so if we managed to get a method inside it, it would work without having to declare @transaction.

If we could guarantee that our users were overriding a method, we could get its reference, but we couldn't force that, and referecing a method from Spec wouldn't work (as Spec doesn't declare the attributes).

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

Fixes #950
@LeoColman
Copy link
Member

I finished it last night and forgot to commit. Take a look at the PR, @fipp

sksamuel pushed a commit that referenced this issue Aug 25, 2019
…#953)

More information at #950

Spring executes transactions (and this include anything inside @DataJpaTest) through a Listener called TransactionalTestExecutionListener. This comes pre-registered with a SpringBootTest, and KotlinTest was executing this listener correctly as per #887.

However, only executing the Listener wasn't enough, as it couldn't autowire the EntityManager. Initially, this was happening because our SpringListener had a fake "test method" that was a pointer to java.lang.Object.hashcode method. We made this hack to allow us to execute code inside Spring Test Framework, and it seemed fine on our tests.

Due to hashcode being a java.lang.Object method, Spring couldn't find the transaction attribute, as it verified directly that the method wasn't from Java Object.

With this in mind, we must use an object from another class. My first attempt was to use a dummy method, declared in the SpringListener itself. But this also wouldn't work, as the listener would never find the TransactionAttribute, as the method doesn't (and shouldn't) declare a @transaction, and SpringListener also didn't declare that annotation. The only way to have a method with that annotation would be inside user's Spec definition, where the user would choose it.

We don't have methods, as we define everything through a DSL. But that didn't seem an issue, as Spring's Listener would get the Transaction Attribute from the method's class if it doesn't declare it. @DataJpaTest already declare transaction attributes, so if we managed to get a method inside it, it would work without having to declare @transaction.

If we could guarantee that our users were overriding a method, we could get its reference, but we couldn't force that, and referecing a method from Spec wouldn't work (as Spec doesn't declare the attributes).

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

Fixes #950
@LeoColman
Copy link
Member

To those waiting for this, try to use the SnapShot builds while this doesn't go to a stable release.

@sksamuel
Copy link
Member

sksamuel commented Sep 4, 2019

Please try 3.4.1

@fipp
Copy link

fipp commented Sep 20, 2019

I'm still getting java.lang.IllegalStateException: No transactional EntityManager found when attempting to use TestEntityManager in a @DataJpaTest using kotlintest (works when using JUnit5). Has anyone gotten this to work in 3.4.1?

@LeoColman
Copy link
Member

@fipp This fix is waiting for release. We're going to release it for 3.4.2. If you test a snapshot build it will work.

LeoColman added a commit that referenced this issue Sep 20, 2019
…#953)

More information at #950

Spring executes transactions (and this include anything inside @DataJpaTest) through a Listener called TransactionalTestExecutionListener. This comes pre-registered with a SpringBootTest, and KotlinTest was executing this listener correctly as per #887.

However, only executing the Listener wasn't enough, as it couldn't autowire the EntityManager. Initially, this was happening because our SpringListener had a fake "test method" that was a pointer to java.lang.Object.hashcode method. We made this hack to allow us to execute code inside Spring Test Framework, and it seemed fine on our tests.

Due to hashcode being a java.lang.Object method, Spring couldn't find the transaction attribute, as it verified directly that the method wasn't from Java Object.

With this in mind, we must use an object from another class. My first attempt was to use a dummy method, declared in the SpringListener itself. But this also wouldn't work, as the listener would never find the TransactionAttribute, as the method doesn't (and shouldn't) declare a @transaction, and SpringListener also didn't declare that annotation. The only way to have a method with that annotation would be inside user's Spec definition, where the user would choose it.

We don't have methods, as we define everything through a DSL. But that didn't seem an issue, as Spring's Listener would get the Transaction Attribute from the method's class if it doesn't declare it. @DataJpaTest already declare transaction attributes, so if we managed to get a method inside it, it would work without having to declare @transaction.

If we could guarantee that our users were overriding a method, we could get its reference, but we couldn't force that, and referecing a method from Spec wouldn't work (as Spec doesn't declare the attributes).

The only way to solve this issue that I found was to inject a dummy method into the class, and get it through reflection, so that Spring Listener would correctly find the Transaction Attributes, and that's what I've done.

Fixes #950
@LeoColman
Copy link
Member

3.4.2 release is out

@fipp
Copy link

fipp commented Sep 23, 2019

I still get the error with 3.4.2. Running StringSpec test on a class annotated with:

@DataJpaTest
@Transactional

The class attempts to autowire org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager. When this is used, the same error occurs: No transactional EntityManager found

@dizney
Copy link

dizney commented Oct 28, 2019

It does work for me, but i need to set IsolationMode to InstancePerTest.
If i use SingleInstance or InstancePerLeaf i get:

java.lang.IllegalStateException: Cannot start new transaction without ending existing transaction
	at org.springframework.util.Assert.state(Assert.java:73)
	at org.springframework.test.context.transaction.TransactionalTestExecutionListener.beforeTestMethod(TransactionalTestExecutionListener.java:180)
	at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:289)
	at io.kotlintest.spring.SpringListener.beforeTest(SpringListener.kt:36)
	at io.kotlintest.runner.jvm.TestCaseExecutor.before(TestCaseExecutor.kt:183)
	at io.kotlintest.runner.jvm.TestCaseExecutor.access$before(TestCaseExecutor.kt:35)
	at io.kotlintest.runner.jvm.TestCaseExecutor$execute$3.invokeSuspend(TestCaseExecutor.kt:49)
	at io.kotlintest.runner.jvm.TestCaseExecutor$execute$3.invoke(TestCaseExecutor.kt)

The TransactionalTestExecutionListener expects every test finished before starting a new test, which is not the case for nested tests when using SingleInstance.

@leonampd
Copy link

Hello everyone,

I'm facing this problem right now even, setting the isolation mode to InstancePerTest as you said @dizney. I tried to execute the tests using the example provided by @tunaranch but I got:

-------------------------------------------------------------------------------
Test set: com.example.demo.chemicals.repository.ElementTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.177 s <<< FAILURE! - in com.example.demo.chemicals.repository.ElementTest
com.example.demo.chemicals.repository.ElementTest  Time elapsed: 0.177 s  <<< ERROR!
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

@sksamuel
Copy link
Member

sksamuel commented Feb 12, 2020 via email

@leonampd
Copy link

Hey @sksamuel here it is. Let me know if you need anything.

@dizney
Copy link

dizney commented Feb 14, 2020

Hey @sksamuel here it is. Let me know if you need anything.

It seems like you are using version 3.3.0; you need at least 3.4.2 for this to work.

@leonampd
Copy link

@dizney thank you! I totally forgot to change the version, my bad. Using the right version (3.4.2) the error still the same. Maybe, you can take a look again here.

stacktrace:

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:293)
	at com.sun.proxy.$Proxy112.persist(Unknown Source)
	at com.example.demo.chemicals.repository.ElementTest$1$1.invokeSuspend(ElementTest.kt:23)
	at com.example.demo.chemicals.repository.ElementTest$1$1.invoke(ElementTest.kt)
	at io.kotlintest.specs.AbstractBehaviorSpec$GivenContext$addWhenContext$2.invokeSuspend(AbstractBehaviorSpec.kt:38)
	at io.kotlintest.specs.AbstractBehaviorSpec$GivenContext$addWhenContext$2.invoke(AbstractBehaviorSpec.kt)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner.locate(InstancePerTestSpecRunner.kt:149)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner$locate$3.registerTestCase(InstancePerTestSpecRunner.kt:151)
	at io.kotlintest.TestContext.registerTestCase(TestContext.kt:52)
	at io.kotlintest.specs.AbstractBehaviorSpec$GivenContext.addWhenContext(AbstractBehaviorSpec.kt:38)
	at io.kotlintest.specs.AbstractBehaviorSpec$GivenContext.When(AbstractBehaviorSpec.kt:34)
	at com.example.demo.chemicals.repository.ElementTest$1.invokeSuspend(ElementTest.kt:22)
	at com.example.demo.chemicals.repository.ElementTest$1.invoke(ElementTest.kt)
	at io.kotlintest.specs.AbstractBehaviorSpec$addGivenContext$1.invokeSuspend(AbstractBehaviorSpec.kt:22)
	at io.kotlintest.specs.AbstractBehaviorSpec$addGivenContext$1.invoke(AbstractBehaviorSpec.kt)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner.locate(InstancePerTestSpecRunner.kt:149)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner$execute$$inlined$let$lambda$1$1.invokeSuspend(InstancePerTestSpecRunner.kt:127)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner$execute$$inlined$let$lambda$1$1.invoke(InstancePerTestSpecRunner.kt)
	at io.kotlintest.runner.jvm.spec.SpecRunner.interceptSpec(SpecRunner.kt:27)
	at io.kotlintest.runner.jvm.spec.SpecRunner.interceptSpec(SpecRunner.kt:53)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner$execute$$inlined$let$lambda$1.invokeSuspend(InstancePerTestSpecRunner.kt:125)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:76)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:53)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner.execute(InstancePerTestSpecRunner.kt:124)
	at io.kotlintest.runner.jvm.spec.InstancePerTestSpecRunner.execute(InstancePerTestSpecRunner.kt:90)
	at io.kotlintest.runner.jvm.spec.SpecExecutor$execute$$inlined$invoke$lambda$1.invoke(SpecExecutor.kt:54)
	at io.kotlintest.runner.jvm.spec.SpecExecutor$execute$$inlined$invoke$lambda$1.invoke(SpecExecutor.kt:20)
	at io.kotlintest.runner.jvm.spec.SpecExecutor.withExecutor(SpecExecutor.kt:31)
	at io.kotlintest.runner.jvm.spec.SpecExecutor.execute(SpecExecutor.kt:37)
	at io.kotlintest.runner.jvm.TestEngine$submitSpec$1.run(TestEngine.kt:111)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

@dizney
Copy link

dizney commented Feb 15, 2020

The test works if you don't use the Then. The problem is that the nested tests of kotlintest don't play well with how the spring TransactionalTestExecutionListener works with transactions. The TransactionalTestExecutionListener creates a transaction before a test method is called and closes the transaction after the test method ended.

When you use the default single instance isolation mode something like this happens:

instantiate spec
beforeTest for Given
create transaction 1
run code in Given
beforeTest for When
try to create transaction 2 but spring's TransactionalTestExecutionListener sees that there is already a transaction so throws an error.

With InstancePerLeaf something similar happens.

With InstancePerTest it's like:
instantiate spec
beforeTest for Given
create transaction 1
run code in Given
afterTest for Given
close transaction 1
run code in Given before When part
beforeTest for When
create transaction 2
run code in When
afterTest for When
close transaction 2
run code in Given before When
run code in When before Then
Here it fails because there is no transaction created

To me i would more expect to be from Given -> When -> Then as one test. Then for that whole part beforeTest would be called once and 1 transaction would be created. In general i would like it more if it worked like that, at least in the BehaviourSpec but i would say also in the other spec types.

TLDR; if you really want to use kotlintest with spring DataJPATest i would stick to 1 level of tests, so no nested tests. Then you can also use any isolation mode you want probably.

@stale
Copy link

stale bot commented Apr 15, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Apr 15, 2020
@stale stale bot closed this as completed Apr 22, 2020
@cldfzn
Copy link

cldfzn commented May 12, 2020

So having run in to this issue pretty consistently, do we have any thoughts around a solution? I've stuck to one depth level, but as soon as I add a second DataJPATest everything falls down again with the lack of an entity manager. I've been trying to think of a solution that's a bit more stable.. could we insert some logic to be knowledgeable about the spring transaction support and ignore that exception in cases where we expect it within the spring listener?

@LeoColman
Copy link
Member

@cldfzn
Could you create a sample project for your issue? It seems a little bit different from what other people said above, as they've solved it with using only top level cases

@cldfzn
Copy link

cldfzn commented May 13, 2020

In creating a small sample I wasn't able to reproduce it. So probably some unintended interaction with something I've done. Thanks for responding.

@sksamuel sksamuel reopened this May 13, 2020
@stale stale bot removed the wontfix label May 13, 2020
@StragaSevera
Copy link

StragaSevera commented Dec 15, 2020

@LeoColman
I'm getting the same error: No transactional EntityManager found. I'm using the 4.3.2 version of Kotest, using only one layer of nesting, etc.
Here is a minimal example that reproduces the problem: https://github.com/StragaSevera/kotest-spring
I'm a newbie with Spring, so maybe I'm doing something wrong. If so, please, help me =-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Issues that report a problem or error in the code.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants