-
Notifications
You must be signed in to change notification settings - Fork 38.4k
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
Support programmatic starting and stopping of transactions in the TestContext framework [SPR-5079] #9753
Comments
Jorg Heymans commented see also http://forum.springsource.org/showthread.php?t=61949 please add setComplete() and endTransaction() back in the test context framework, they are incredibly useful for example to test correct lazy loading behaviour of DTO's |
Edwin Dhondt commented Is there any means to get this in a 2.5.x release so we don't have to do a major upgrade ? |
Sam Brannen commented Hi Edwin, No, this functionality will not be backported to 2.5.x. Current development is only performed on the 3.x branch. For 2.5.x your only option would be to implement your own custom TransactionalTestExecutionListener which makes programmatic access to transaction management available to your test instance. Regards, Sam |
Edwin Dhondt commented Sam, In which 3.x milestone build will this support be exactly available or is it already available ? Back to Spring 2.5.6.
Please advice. Thanks, |
Sam Brannen commented Edwin,
Based on your described use case, I assume that you do not care about corrupting (i.e., permanently changing) the state of the database. So if my assumption is correct, you can...
Hope this helps, Sam |
Jim commented There is a whole category of tests that require programmatic control of transactions (potentially even multiple transactions). Sam's solution is very inconvenient because it requires a separate class for each test case, given that the AfterTransaction is class-wide. |
Sam Brannen commented Jim,
The steps I outlined are not intended to be a solution per se but rather a work-around until we have added the desired programmatic support to the framework. Regards, Sam |
Jim commented Understood, no offence intended. |
Julian commented For those coming to this ticket and interested in a workaround give my code below a try. It is less than ideal since it uses reflection and unlocks private methods, but it works. Pay attention to the semantics of calling these methods as documented in the my javadoc comments. This can be placed in your test class or a parent class. I have TestNG annotations on the start and end transaction methods, but these can be easily replaced with JUnit
|
Julian commented One update, the e.printStackTrace calls aren't needed and can be removed from my workaround. |
daniel carter commented In my case i setup some test data for an integration test. Each piece of data represents an event, and the code under test starts a new transaction to process each event. As the test data is uncommited, the code under test deadlocks when it tries to mark the event as processed. My 'workaround' for this issue was to simply call commit on the active connection after creating the test data.
|
Val Blant commented Hmm. 6 years since this problem was reported, and still no official solution? In any case, here's how to get the endTransaction() and startNewTransaction() methods back. This solution is based on Julian's post.
/**
* There is a regression in Spring 4, which removes the APIs for manual transaction control
* in tests. So calls like endTransaction() and startNewTransaction() are no longer available.
*
* Since this functionality is still required by some of our integration tests, this custom
* Junit 4 Runner is necessary to store a reference to the current
* <code>org.springframework.test.context.TestContextManager</code>. This gives us the ability
* to manipulate transactions directly, thus restoring the lost functionality.
*
* https://jira.springsource.org/browse/SPR-5079
*
* @author Val Blant
*/
public class SpringTestContextManagerAwareClassRunner extends SpringJUnit4ClassRunner {
public SpringTestContextManagerAwareClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
@Override
protected Object createTest() throws Exception {
Object testInstance = super.createTest();
if ( testInstance instanceof AbstractTestCase ) {
((AbstractTestCase)testInstance).setTestContextManager( getTestContextManager() );
}
return testInstance;
}
}
@RunWith(SpringTestContextManagerAwareClassRunner.class)
public abstract class AbstractTestCase extends AbstractTransactionalJUnit4SpringContextTests {
private TestContextManager testContextManager;
public void setTestContextManager(TestContextManager testContextManager) {
this.testContextManager = testContextManager;
}
/**
* This is a helper method to allow us to programmatically stop a running
* transaction. Calling this method will also cause any methods marked as @AfterTransaction
* to fire.
*
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener#afterTestMethod(TestContext)
*/
protected void endTransaction() {
final TransactionalTestExecutionListener tel = _GetTransactionalTestExecutionListener();
try {
tel.afterTestMethod(_GetTestContext());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* This is a helper method to allow us to programmatically start a running
* transaction. Calling this method will also cause any methods marked as @BeforeTransaction
* to fire.
*
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener#beforeTestMethod(TestContext)
*/
protected void startNewTransaction() {
final TransactionalTestExecutionListener tel = _GetTransactionalTestExecutionListener();
try {
tel.beforeTestMethod(_GetTestContext());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
private static <T> T _GetField(Object target, String fieldName) {
assertNotNull(target);
final Field field = ReflectionUtils.findField(target.getClass(), fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + target + "]");
}
ReflectionUtils.makeAccessible(field);
return (T) ReflectionUtils.getField(field, target);
}
private TransactionalTestExecutionListener _GetTransactionalTestExecutionListener() {
for (TestExecutionListener tel : testContextManager.getTestExecutionListeners()) {
if (tel instanceof TransactionalTestExecutionListener) {
return (TransactionalTestExecutionListener) tel;
}
}
throw new RuntimeException("TransactionalTestExecutionListener not found!");
}
private TestContext _GetTestContext() {
final TestContext testContext = _GetField(testContextManager, "testContext");
return testContext;
}
} |
Dennis Kieselhorst commented Thanks for the sample. As I'm using |
Sam Brannen commented FYI: the current work on this feature can be seen on my #9753 branch on GitHub. Feedback is welcome (and encouraged)! :) Cheers, Sam |
Sam Brannen commented Completed as described in the comments for GitHub commit f667e43:
|
Dennis Kieselhorst commented Just tried it out, this solution is easy to use and works fine for me. Thanks! |
Sam Brannen commented Dennis Kieselhorst, I'm very glad to hear that. :) Thanks for trying it out and reporting back! |
Den Orlov opened SPR-5079 and commented
Background
I have integration tests that:
When I used Spring's "JUnit 3.8 legacy support" all of these three steps were organized in one test method using the protected
endTransaction()
andstartNewTransaction()
methods inAbstractTransactionalSpringContextTests
between steps #1 and #2.Status Quo
Now I am migrating my code to use the Spring TestContext Framework (TCF), but it doesn't provide support for programmatically starting or stopping the test-managed transaction.
Deliverables
The term test-manged means a transaction managed by the TCF, either declaratively or programmatically.
See also "Design Considerations" below.
TransactionalTestExecutionListener
to use this new mechanism internally.TestRule
that simplifies the programming model (e.g., by delegating to the proposedTestTransaction
façade).Design Considerations
Introduce a
TestTransaction
class that acts as a façade to the functionality previously available inTransactionalTestExecutionListener
. For example,TestTransaction
could define methods that could provide the following API:TestTransaction.start()
TestTransaction.end()
TestTransaction.rollback(boolean)
Options for Interacting with the Test Transaction
Dependency Injection of TestTransaction
Ideally, we would like to be able to have the
TestTransaction
injected into our test instance. However, since theDependencyInjectionTestExecutionListener
must come before theTransactionalTestExecutionListener
in the chain of listeners, dependency injection would only be possible by introducing yet another "transactional"TestExecutionListener
that creates a proxy bean or bean of typeObjectFactory
in theApplicationContext
. Such a proxy bean would serve as a placeholder for dependency injection into the test instance, and theTransactionalTestExecutionListener
could later set a value either directly in the proxy/ObjectFactory
or via aThreadLocal
.But we would like to avoid having two (2)
TestExecutionListener
implementations for transactional support. Plus, the placeholder-bean approach could present more problems than it solves.Dependency Injection of TestContext
A second option would be to inject the
TestContext
into test instances (see #12947) and provide access to theTestTransaction
as an attribute (note thatTestContext
implementsAttributeAccessor
), but this would open up use of theTestContext
within test classes (i.e., no longer limited to theTestExecutionListener
API). In addition, developers would be forced to navigate theTestContext
to obtain theTestTransaction
, thus making the programming model less intuitive.Purely ThreadLocal Approach
The final option is to follow a purely
ThreadLocal
-based approach withTestTransaction
encapsulating the details and providing static methods instead of instance methods.Related Resources
TransactionContext
keyed byMethod
and removed the previous support for thetransactionFlaggedForCommit
functionality.Affects: 2.5.6
Issue Links:
Referenced from: commits bdceaa4, f667e43, 90f0d14
30 votes, 22 watchers
The text was updated successfully, but these errors were encountered: