Skip to content

Commit f2ac857

Browse files
committed
HHH-19912 Add automatic test data cleanup feature to @SessionFactory annotation
1 parent 8a56476 commit f2ac857

File tree

6 files changed

+467
-2
lines changed

6 files changed

+467
-2
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.orm.junit;
6+
7+
/**
8+
* Enumeration of when to drop test data automatically.
9+
*
10+
* @author inpink
11+
*/
12+
public enum DropDataTiming {
13+
/**
14+
* Drop test data before each test method
15+
*/
16+
BEFORE_EACH,
17+
18+
/**
19+
* Drop test data after each test method
20+
*/
21+
AFTER_EACH,
22+
23+
/**
24+
* Drop test data before all test methods (once per test class)
25+
*/
26+
BEFORE_ALL,
27+
28+
/**
29+
* Drop test data after all test methods (once per test class)
30+
*/
31+
AFTER_ALL
32+
}

hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
/**
2121
* @author Steve Ebersole
22+
* @author inpink
2223
*/
2324
@Inherited
2425
@Target({ElementType.TYPE, ElementType.METHOD})
@@ -56,4 +57,12 @@
5657
boolean useCollectingStatementInspector() default false;
5758

5859
boolean applyCollectionsInDefaultFetchGroup() default true;
60+
61+
/**
62+
* When to automatically drop test data. Multiple timing values can be specified
63+
* to drop data at different points in the test lifecycle.
64+
*
65+
* @return the timing(s) for dropping test data
66+
*/
67+
DropDataTiming[] dropTestData() default {};
5968
}

hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
2323
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping;
2424
import org.jboss.logging.Logger;
25+
import org.junit.jupiter.api.extension.AfterAllCallback;
26+
import org.junit.jupiter.api.extension.AfterEachCallback;
27+
import org.junit.jupiter.api.extension.BeforeAllCallback;
2528
import org.junit.jupiter.api.extension.BeforeEachCallback;
2629
import org.junit.jupiter.api.extension.ExtensionContext;
2730
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
@@ -43,12 +46,15 @@
4346
* @see DomainModelExtension
4447
*
4548
* @author Steve Ebersole
49+
* @author inpink
4650
*/
4751
public class SessionFactoryExtension
48-
implements TestInstancePostProcessor, BeforeEachCallback, TestExecutionExceptionHandler {
52+
implements TestInstancePostProcessor, BeforeAllCallback, BeforeEachCallback,
53+
AfterEachCallback, AfterAllCallback, TestExecutionExceptionHandler {
4954

5055
private static final Logger log = Logger.getLogger( SessionFactoryExtension.class );
5156
private static final String SESSION_FACTORY_KEY = SessionFactoryScope.class.getName();
57+
private static final String DROP_DATA_TIMING_KEY = "DROP_DATA_TIMING";
5258

5359
/**
5460
* Intended for use from external consumers. Will never create a scope, just
@@ -77,12 +83,18 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext contex
7783
|| SessionFactoryProducer.class.isAssignableFrom( context.getRequiredTestClass() ) ) {
7884
final DomainModelScope domainModelScope = DomainModelExtension.getOrCreateDomainModelScope( testInstance, context );
7985
final SessionFactoryScope created = createSessionFactoryScope( testInstance, sfAnnRef, domainModelScope, context );
80-
locateExtensionStore( testInstance, context ).put( SESSION_FACTORY_KEY, created );
86+
final ExtensionContext.Store store = locateExtensionStore( testInstance, context );
87+
store.put( SESSION_FACTORY_KEY, created );
88+
89+
final DropDataTiming[] dropDataTimings = resolveDropDataTimings( testInstance, sfAnnRef );
90+
store.put( DROP_DATA_TIMING_KEY, dropDataTimings );
8191
}
8292
}
8393

8494
@Override
8595
public void beforeEach(ExtensionContext context) {
96+
handleDropData(context, DropDataTiming.BEFORE_EACH);
97+
8698
final Optional<SessionFactory> sfAnnRef = AnnotationSupport.findAnnotation(
8799
context.getRequiredTestMethod(),
88100
SessionFactory.class
@@ -97,6 +109,8 @@ public void beforeEach(ExtensionContext context) {
97109
final DomainModelScope domainModelScope = DomainModelExtension.resolveForMethodLevelSessionFactoryScope( context );
98110
final SessionFactoryScope created = createSessionFactoryScope( context.getRequiredTestInstance(), sfAnnRef, domainModelScope, context );
99111
final ExtensionContext.Store extensionStore = locateExtensionStore( context.getRequiredTestInstance(), context );
112+
final DropDataTiming[] dropDataTimings = resolveDropDataTimings( context.getRequiredTestInstance(), sfAnnRef );
113+
extensionStore.put( DROP_DATA_TIMING_KEY, dropDataTimings );
100114
extensionStore.put( SESSION_FACTORY_KEY, created );
101115
}
102116

@@ -174,6 +188,20 @@ else if ( ! explicitInspectorClass.equals( StatementInspector.class ) ) {
174188
return sfScope;
175189
}
176190

191+
private static DropDataTiming[] resolveDropDataTimings(
192+
Object testInstance,
193+
Optional<SessionFactory> sfAnnRef) {
194+
if ( testInstance instanceof SessionFactoryProducer ) {
195+
return ((SessionFactoryProducer) testInstance).dropTestData();
196+
}
197+
else if ( sfAnnRef.isPresent() ) {
198+
return sfAnnRef.get().dropTestData();
199+
}
200+
else {
201+
return new DropDataTiming[]{};
202+
}
203+
}
204+
177205
private static void prepareSchemaExport(
178206
SessionFactoryImplementor sessionFactory,
179207
MetadataImplementor model,
@@ -231,6 +259,43 @@ public void handleTestExecutionException(ExtensionContext context, Throwable thr
231259
throw throwable;
232260
}
233261

262+
@Override
263+
public void beforeAll(ExtensionContext context) throws Exception {
264+
log.debugf("BEFORE_ALL callback invoked for %s", context.getDisplayName());
265+
handleDropData(context, DropDataTiming.BEFORE_ALL);
266+
}
267+
268+
@Override
269+
public void afterEach(ExtensionContext context) throws Exception {
270+
handleDropData(context, DropDataTiming.AFTER_EACH);
271+
}
272+
273+
@Override
274+
public void afterAll(ExtensionContext context) throws Exception {
275+
handleDropData(context, DropDataTiming.AFTER_ALL);
276+
}
277+
278+
private void handleDropData(ExtensionContext context, DropDataTiming timing) {
279+
try {
280+
final Object testInstance = context.getRequiredTestInstance();
281+
final ExtensionContext.Store store = locateExtensionStore(testInstance, context);
282+
283+
final DropDataTiming[] configuredTimings = (DropDataTiming[]) store.get( DROP_DATA_TIMING_KEY );
284+
285+
for (DropDataTiming configuredTiming : configuredTimings) {
286+
if (configuredTiming == timing) {
287+
final SessionFactoryScope scope = findSessionFactoryScope(testInstance, context);
288+
scope.dropData();
289+
log.debugf("Dropped data at timing %s for %s", timing, context.getDisplayName());
290+
break;
291+
}
292+
}
293+
}
294+
catch (Exception e) {
295+
log.debugf("Failed to drop data at timing %s: %s", timing, e.getMessage());
296+
}
297+
}
298+
234299
private static class SessionFactoryScopeImpl implements SessionFactoryScope, AutoCloseable {
235300
private final DomainModelScope modelScope;
236301
private final SessionFactoryProducer producer;

hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,11 @@
2424
* @author Steve Ebersole
2525
*/
2626
public interface SessionFactoryProducer {
27+
DropDataTiming[] NONE = new DropDataTiming[0];
28+
2729
SessionFactoryImplementor produceSessionFactory(MetadataImplementor model);
30+
31+
default DropDataTiming[] dropTestData() {
32+
return NONE;
33+
}
2834
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.annotations.methods;
6+
7+
import org.hibernate.testing.annotations.AnEntity;
8+
import org.hibernate.testing.orm.junit.DomainModel;
9+
import org.hibernate.testing.orm.junit.DropDataTiming;
10+
import org.hibernate.testing.orm.junit.SessionFactory;
11+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
12+
import org.junit.jupiter.api.MethodOrderer;
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Order;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.TestMethodOrder;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
/**
21+
* Tests for dropTestData timing configuration.
22+
*
23+
* @author inpink
24+
*/
25+
public class DropDataTimingTest {
26+
27+
@Nested
28+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
29+
@DomainModel(annotatedClasses = AnEntity.class)
30+
@SessionFactory(dropTestData = {DropDataTiming.AFTER_EACH})
31+
class AfterEachDropDataTests {
32+
33+
@Test
34+
@Order(1)
35+
public void testAfterEachDropData(SessionFactoryScope scope) {
36+
scope.inTransaction(session -> {
37+
AnEntity entity = new AnEntity(1, "After Each");
38+
session.persist(entity);
39+
});
40+
41+
scope.inTransaction(session -> {
42+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
43+
.getSingleResult();
44+
assertThat(count).isEqualTo(1L);
45+
});
46+
}
47+
48+
@Test
49+
@Order(2)
50+
public void verifyAfterEachDropDataCleanup(SessionFactoryScope scope) {
51+
scope.inTransaction(session -> {
52+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
53+
.getSingleResult();
54+
assertThat(count).isEqualTo(0L);
55+
});
56+
}
57+
}
58+
59+
@Nested
60+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
61+
@DomainModel(annotatedClasses = AnEntity.class)
62+
@SessionFactory(dropTestData = {DropDataTiming.BEFORE_EACH})
63+
class BeforeEachDropDataTests {
64+
65+
@Test
66+
@Order(1)
67+
public void prepareBeforeEachDropData(SessionFactoryScope scope) {
68+
scope.inTransaction(session -> {
69+
AnEntity entity = new AnEntity(2, "Before Each");
70+
session.persist(entity);
71+
});
72+
}
73+
74+
@Test
75+
@Order(2)
76+
public void testBeforeEachDropData(SessionFactoryScope scope) {
77+
scope.inTransaction(session -> {
78+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
79+
.getSingleResult();
80+
assertThat(count).isEqualTo(0L);
81+
});
82+
}
83+
}
84+
85+
@Nested
86+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
87+
@DomainModel(annotatedClasses = AnEntity.class)
88+
@SessionFactory
89+
class NeverDropDataTests {
90+
91+
@Test
92+
@Order(1)
93+
public void prepareNeverDropData(SessionFactoryScope scope) {
94+
scope.inTransaction(session -> {
95+
AnEntity entity = new AnEntity(3, "Never");
96+
session.persist(entity);
97+
});
98+
}
99+
100+
@Test
101+
@Order(2)
102+
public void testNeverDropData(SessionFactoryScope scope) {
103+
scope.inTransaction(session -> {
104+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
105+
.getSingleResult();
106+
assertThat(count).isGreaterThan(0L);
107+
});
108+
109+
}
110+
}
111+
112+
@Nested
113+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
114+
@DomainModel(annotatedClasses = AnEntity.class)
115+
class MethodLevelDropDataTests {
116+
117+
@Test
118+
@Order(1)
119+
@SessionFactory(dropTestData = {DropDataTiming.AFTER_EACH})
120+
public void methodLevelAfterEach(SessionFactoryScope scope) {
121+
scope.inTransaction(session -> {
122+
AnEntity entity = new AnEntity(10, "Method After Each");
123+
session.persist(entity);
124+
});
125+
126+
scope.inTransaction(session -> {
127+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
128+
.getSingleResult();
129+
assertThat(count).isEqualTo(1L);
130+
});
131+
}
132+
133+
@Test
134+
@Order(2)
135+
@SessionFactory(dropTestData = {DropDataTiming.AFTER_EACH})
136+
public void methodLevelAfterEachCleanup(SessionFactoryScope scope) {
137+
scope.inTransaction(session -> {
138+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
139+
.getSingleResult();
140+
assertThat(count).isEqualTo(0L);
141+
});
142+
}
143+
144+
@Test
145+
@Order(3)
146+
@SessionFactory(dropTestData = {DropDataTiming.BEFORE_EACH})
147+
public void methodLevelBeforeEachInsert(SessionFactoryScope scope) {
148+
scope.inTransaction(session -> {
149+
AnEntity entity = new AnEntity(11, "Method Before Each");
150+
session.persist(entity);
151+
});
152+
}
153+
154+
@Test
155+
@Order(4)
156+
@SessionFactory(dropTestData = {DropDataTiming.BEFORE_EACH})
157+
public void methodLevelBeforeEachVerify(SessionFactoryScope scope) {
158+
scope.inTransaction(session -> {
159+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
160+
.getSingleResult();
161+
assertThat(count).isEqualTo(0L);
162+
});
163+
}
164+
165+
@Test
166+
@Order(5)
167+
@SessionFactory
168+
public void methodLevelNeverInsert(SessionFactoryScope scope) {
169+
scope.inTransaction(session -> {
170+
AnEntity entity = new AnEntity(12, "Method Never");
171+
session.persist(entity);
172+
});
173+
}
174+
175+
@Test
176+
@Order(6)
177+
@SessionFactory
178+
public void methodLevelNeverVerify(SessionFactoryScope scope) {
179+
scope.inTransaction(session -> {
180+
Long count = session.createQuery("select count(e) from AnEntity e", Long.class)
181+
.getSingleResult();
182+
assertThat(count).isEqualTo(0L);
183+
});
184+
185+
scope.dropData();
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)