Open elgabbouch opened 5 months ago
Could you please provide a minimal sample project for reproducing this issue.
It's obvois that previous transaction wasn't closed properly, and so (most likely) some spring junit extension wasn't called at the right time (or at the expected sequence, or its a junit-store related issue). Only debugging on a real project could help in this case (to compare junit and spock behaviours).
I used this sample project: https://github.com/mkyong/spring-boot/tree/master/spring-data-jpa
Modified pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jpa</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Spring Data JPA</name>
<url>https://mkyong.com</url>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<properties>
<java.version>17</java.version>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</properties>
<dependencies>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- in-memory database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- ADDED -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>2.3-groovy-4.0</version>
<scope>test</scope>
</dependency>
<!-- ADDED -->
<dependency>
<groupId>ru.vyarus</groupId>
<artifactId>spock-junit5</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- ADDED -->
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<goals>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
(added gmavenplus plugin and spock and spock-junit5 dependencies; note that I did not used spock-spring!)
Next I rewritten BaseRepositoryTest into spock specification:
package com.mkyong
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import spock.lang.Specification
import java.time.LocalDate
import static org.junit.jupiter.api.Assertions.assertEquals
@DataJpaTest
class BootTest extends Specification {
// Alternative for EntityManager
// Optional in this case, we can use bookRepository to do the same stuff
@Autowired
private TestEntityManager testEM;
@Autowired
private BookRepository bookRepository;
void setup() {
bookRepository.deleteAll();
bookRepository.flush();
testEM.clear();
}
def "Test save"() {
when:
Book b1 = new Book("Book A", BigDecimal.valueOf(9.99), LocalDate.of(2023, 8, 31));
//testEM.persistAndFlush(b1); the same
bookRepository.save(b1);
Long savedBookID = b1.getId();
Book book = bookRepository.findById(savedBookID).orElseThrow();
// Book book = testEM.find(Book.class, savedBookID);
then:
savedBookID == book.getId()
"Book A" == book.getTitle()
BigDecimal.valueOf(9.99) == book.getPrice()
LocalDate.of(2023, 8, 31) == book.getPublishDate()
}
def "Test update"() {
when:
Book b1 = new Book("Book A", BigDecimal.valueOf(9.99), LocalDate.of(2023, 8, 31));
//testEM.persistAndFlush(b1);
bookRepository.save(b1);
// update price from 9.99 to 19.99
b1.setPrice(BigDecimal.valueOf(19.99));
bookRepository.save(b1);
List<Book> result = bookRepository.findByTitle("Book A");
then:
1 == result.size()
Book book = result.get(0);
book.getId() != null
book.getId() > 0
assertEquals("Book A", book.getTitle());
assertEquals(BigDecimal.valueOf(19.99), book.getPrice());
assertEquals(LocalDate.of(2023, 8, 31), book.getPublishDate());
}
}
(used only first 2 test methods)
And it works. Did I miss anything?
I'm running into the same issue after upgrading to spring 6. Trying to isolate exactly what is going on, but it seems to only trigger after running many tests, and specifically after a timeout when using (new PollingConditions(timeout: 15)).eventually
. Once it triggers those, most all other tests fail. I wonder if there is an event that is not being handled here or something?
According to SpringExtension source:
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
ParameterResolver {
All required jupiter extensions supported. Moreover, TransactionalTestExecutionListener (throwing exception above) relies on BeforeEachCallback
and AfterEachCallback
to start and stop transactions. Super simple and should work.
I also verified that AfterEachCallback
is called even in case of exception in spock test method.
The issue might be caused by spock lifecicle specifics, but I have no idea what it could be. I tried to experiment with PollingConditions
with no success. I need a starting point: project, reproducing the problem.
If you can prepare such example reproducing this problem (or could share your project) I would investigate it.
I finally think I figured this out, and you said it in the comment, but I was working on upgrading an old project and didn't realize it had spring-spock
on it as well. Once I removed spock-junit5, everything started working properly. Unfortunately, I need the spring extension since I had a lot of dep injection in there, but I think both in there at the same time were causing duplicate transaction management or something.
You don't need spock-spring for dependency injection! When you use spock-junit5 spring's junit 5 extension would manage dependency injection in test! I tested this - it works.
The extension does not seem to support transactions when using spring jpa I am getting the following exception:
java.lang.IllegalStateException: Cannot start new transaction without ending existing transaction at org.springframework.util.Assert.state(Assert.java:76) at org.springframework.test.context.transaction.TransactionalTestExecutionListener.beforeTestMethod(TransactionalTestExecutionListener.java:204) at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:320) at org.springframework.test.context.junit.jupiter.SpringExtension.beforeEach(SpringExtension.java:240) at ru.vyarus.spock.jupiter.interceptor.JunitApiExecutor.lambda$beforeEach$4(JunitApiExecutor.java:75) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at ru.vyarus.spock.jupiter.interceptor.JunitApiExecutor.beforeEach(JunitApiExecutor.java:75) at ru.vyarus.spock.jupiter.interceptor.ExtensionLifecycleMerger.interceptSetupMethod(ExtensionLifecycleMerger.java:127) at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:30) at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101) at org.spockframework.spring.SpringInterceptor.interceptSetupMethod(SpringInterceptor.java:55) at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:30) at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:148) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156) at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:102) at ru.vyarus.spock.jupiter.JunitExtensionSupport.lambda$visitSpec$1(JunitExtensionSupport.java:112) at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:119) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:94) at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:89) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)