Skip to content

Commit e61884e

Browse files
committed
Introduce tests for "No transaction in progress for @⁠Nested test class"
Since we now officially support the TEST_METHOD ExtensionContextScope (see gh-35676 and gh-35680), this commit introduces tests which demonstrate that the issue raised in gh-34576 is no longer an "issue" if the user indirectly configures the SpringExtension to use the TEST_METHOD scope via the "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default" configuration parameter.
1 parent 0f2fc79 commit e61884e

File tree

5 files changed

+183
-52
lines changed

5 files changed

+183
-52
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.orm.jpa;
18+
19+
import javax.sql.DataSource;
20+
21+
import jakarta.persistence.EntityManagerFactory;
22+
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.jdbc.core.JdbcTemplate;
26+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
27+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
28+
import org.springframework.orm.jpa.JpaTransactionManager;
29+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
30+
import org.springframework.orm.jpa.vendor.Database;
31+
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
32+
import org.springframework.test.context.orm.jpa.domain.JpaPersonRepository;
33+
import org.springframework.test.context.orm.jpa.domain.Person;
34+
import org.springframework.transaction.annotation.EnableTransactionManagement;
35+
36+
@Configuration(proxyBeanMethods = false)
37+
@EnableTransactionManagement
38+
class JpaConfig {
39+
40+
@Bean
41+
JpaPersonRepository personRepository() {
42+
return new JpaPersonRepository();
43+
}
44+
45+
@Bean
46+
EmbeddedDatabase dataSource() {
47+
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
48+
}
49+
50+
@Bean
51+
JdbcTemplate jdbcTemplate(DataSource dataSource) {
52+
return new JdbcTemplate(dataSource);
53+
}
54+
55+
@Bean
56+
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
57+
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
58+
emfb.setDataSource(dataSource);
59+
emfb.setPackagesToScan(Person.class.getPackage().getName());
60+
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
61+
hibernateJpaVendorAdapter.setGenerateDdl(true);
62+
hibernateJpaVendorAdapter.setDatabase(Database.H2);
63+
emfb.setJpaVendorAdapter(hibernateJpaVendorAdapter);
64+
return emfb;
65+
}
66+
67+
@Bean
68+
JpaTransactionManager transactionManager(EntityManagerFactory emf) {
69+
return new JpaTransactionManager(emf);
70+
}
71+
72+
}

spring-test/src/test/java/org/springframework/test/context/orm/jpa/JpaEntityListenerTests.java

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,18 @@
1818

1919
import java.util.List;
2020

21-
import javax.sql.DataSource;
22-
2321
import jakarta.persistence.EntityManager;
24-
import jakarta.persistence.EntityManagerFactory;
2522
import jakarta.persistence.PersistenceContext;
2623
import org.junit.jupiter.api.BeforeEach;
2724
import org.junit.jupiter.api.Test;
2825

2926
import org.springframework.beans.factory.annotation.Autowired;
30-
import org.springframework.context.annotation.Bean;
31-
import org.springframework.context.annotation.Configuration;
3227
import org.springframework.jdbc.core.JdbcTemplate;
33-
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
34-
import org.springframework.orm.jpa.JpaTransactionManager;
35-
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
36-
import org.springframework.orm.jpa.vendor.Database;
37-
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
3828
import org.springframework.test.context.jdbc.Sql;
3929
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
40-
import org.springframework.test.context.orm.jpa.domain.JpaPersonRepository;
4130
import org.springframework.test.context.orm.jpa.domain.Person;
4231
import org.springframework.test.context.orm.jpa.domain.PersonListener;
4332
import org.springframework.test.context.orm.jpa.domain.PersonRepository;
44-
import org.springframework.transaction.annotation.EnableTransactionManagement;
4533
import org.springframework.transaction.annotation.Transactional;
4634

4735
import static org.assertj.core.api.Assertions.assertThat;
@@ -55,7 +43,7 @@
5543
* @see <a href="https://github.com/spring-projects/spring-framework/issues/28228">issue gh-28228</a>
5644
* @see org.springframework.test.context.orm.hibernate.HibernateSessionFlushingTests
5745
*/
58-
@SpringJUnitConfig
46+
@SpringJUnitConfig(JpaConfig.class)
5947
@Transactional
6048
@Sql(statements = "insert into person(id, name) values(0, 'Jane')")
6149
class JpaEntityListenerTests {
@@ -156,43 +144,4 @@ private void assertPeople(String... expectedNames) {
156144
}
157145
}
158146

159-
160-
@Configuration(proxyBeanMethods = false)
161-
@EnableTransactionManagement
162-
static class Config {
163-
164-
@Bean
165-
PersonRepository personRepository() {
166-
return new JpaPersonRepository();
167-
}
168-
169-
@Bean
170-
DataSource dataSource() {
171-
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
172-
}
173-
174-
@Bean
175-
JdbcTemplate jdbcTemplate(DataSource dataSource) {
176-
return new JdbcTemplate(dataSource);
177-
}
178-
179-
@Bean
180-
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
181-
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
182-
emfb.setDataSource(dataSource);
183-
emfb.setPackagesToScan(Person.class.getPackage().getName());
184-
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
185-
hibernateJpaVendorAdapter.setGenerateDdl(true);
186-
hibernateJpaVendorAdapter.setDatabase(Database.HSQL);
187-
emfb.setJpaVendorAdapter(hibernateJpaVendorAdapter);
188-
return emfb;
189-
}
190-
191-
@Bean
192-
JpaTransactionManager transactionManager(EntityManagerFactory emf) {
193-
return new JpaTransactionManager(emf);
194-
}
195-
196-
}
197-
198147
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.orm.jpa;
18+
19+
import jakarta.persistence.EntityManager;
20+
import jakarta.persistence.PersistenceContext;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Nested;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
25+
import org.junit.platform.suite.api.ConfigurationParameter;
26+
import org.junit.platform.suite.api.SelectClasses;
27+
import org.junit.platform.suite.api.Suite;
28+
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.test.context.TestPropertySource;
31+
import org.springframework.test.context.jdbc.Sql;
32+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
33+
import org.springframework.test.context.orm.jpa.domain.Person;
34+
import org.springframework.test.context.orm.jpa.domain.PersonRepository;
35+
import org.springframework.transaction.annotation.Transactional;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Test {@link Suite @Suite} which selects a single test class and runs it with
41+
* {@link ExtensionContextScope#TEST_METHOD}.
42+
*
43+
* @author Sam Brannen
44+
* @since 6.2.13
45+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34576">issue gh-34576</a>
46+
*/
47+
@Suite
48+
@SelectClasses(JpaPersonRepositoryTests.TestCase.class)
49+
@ConfigurationParameter(
50+
key = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME,
51+
value = "test_method" // If this is changed to "default", NestedTests will fail.
52+
)
53+
// Even though this is a @Suite, it is intentionally named JpaPersonRepositoryTests
54+
// instead of JpaPersonRepositoryTestSuite, so that it is run with the Gradle build
55+
// due to the "*Tests" naming convention.
56+
class JpaPersonRepositoryTests {
57+
58+
/**
59+
* Transactional tests for JPA support with {@link Nested @Nested} test classes.
60+
*/
61+
@SpringJUnitConfig(JpaConfig.class)
62+
@Transactional
63+
@Sql(statements = "insert into person(id, name) values(0, 'Jane')")
64+
static class TestCase {
65+
66+
@PersistenceContext
67+
EntityManager em;
68+
69+
@Autowired
70+
PersonRepository repo;
71+
72+
73+
@BeforeEach
74+
void setup() {
75+
em.persist(new Person("John"));
76+
em.flush();
77+
}
78+
79+
@Test
80+
void findAll() {
81+
assertThat(repo.findAll()).map(Person::getName).containsExactlyInAnyOrder("Jane", "John");
82+
}
83+
84+
85+
@Nested
86+
// Declare a random test property to ensure we get a different ApplicationContext.
87+
@TestPropertySource(properties = "nested = true")
88+
class NestedTests {
89+
90+
@Test
91+
void findAll() {
92+
assertThat(repo.findAll()).map(Person::getName).containsExactlyInAnyOrder("Jane", "John");
93+
}
94+
}
95+
96+
}
97+
98+
}

spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/JpaPersonRepository.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.test.context.orm.jpa.domain;
1818

19+
import java.util.List;
20+
1921
import jakarta.persistence.EntityManager;
2022
import jakarta.persistence.PersistenceContext;
2123

@@ -35,6 +37,12 @@ public class JpaPersonRepository implements PersonRepository {
3537
@PersistenceContext
3638
private EntityManager entityManager;
3739

40+
41+
@Override
42+
public List<Person> findAll() {
43+
return this.entityManager.createQuery("from Person", Person.class).getResultList();
44+
}
45+
3846
@Override
3947
public Person findById(Long id) {
4048
return this.entityManager.find(Person.class, id);

spring-test/src/test/java/org/springframework/test/context/orm/jpa/domain/PersonRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.test.context.orm.jpa.domain;
1818

19+
import java.util.List;
20+
1921
/**
2022
* Person repository API.
2123
*
@@ -24,6 +26,8 @@
2426
*/
2527
public interface PersonRepository {
2628

29+
List<Person> findAll();
30+
2731
Person findById(Long id);
2832

2933
Person findByName(String name);

0 commit comments

Comments
 (0)