using System;
using Spring.Transaction;
using Spring.Transaction.Support;
namespace Spring.Testing.NUnit{
/// <summary>
/// Convenient superclass for tests that should occur in a transaction, but normally
/// will roll the transaction back on the completion of each test.
/// </summary>
/// <remarks>
/// <p>This is useful in a range of circumstances, allowing the following benefits:</p>
/// <ul>
/// <li>Ability to delete or insert any data in the database, without affecting other tests</li>
/// <li>Providing a transactional context for any code requiring a transaction</li>
/// <li>Ability to write anything to the database without any need to clean up.</li>
/// </ul>
///
/// <p>This class is typically very fast, compared to traditional setup/teardown scripts.</p>
///
/// <p>If data should be left in the database, call the <code>SetComplete()</code>
/// method in each test. The "DefaultRollback" property, which defaults to "true",
/// determines whether transactions will complete by default.</p>
///
/// <p>It is even possible to end the transaction early; for example, to verify lazy
/// loading behavior of an O/R mapping tool. (This is a valuable away to avoid
/// unexpected errors when testing a web UI, for example.) Simply call the
/// <code>endTransaction()</code> method. Execution will then occur without a
/// transactional context.</p>
///
/// <p>The <code>StartNewTransaction()</code> method may be called after a call to
/// <code>EndTransaction()</code> if you wish to create a new transaction, quite
/// independent of the old transaction. The new transaction's default fate will be to
/// roll back, unless <code>setComplete()</code> is called again during the scope of the
/// new transaction. Any number of transactions may be created and ended in this way.
/// The final transaction will automatically be rolled back when the test case is
/// torn down.</p>
///
/// <p>Transactional behavior requires a single object in the context implementing the
/// IPlatformTransactionManager interface. This will be set by the superclass's
/// Dependency Injection mechanism. If using the superclass's Field Injection mechanism,
/// the implementation should be named "transactionManager". This mechanism allows the
/// use of this superclass even when there's more than one transaction manager in the context.</p>
///
/// <p><i>This superclass can also be used without transaction management, if no
/// IPlatformTransactionManager object is found in the context provided. Be careful about
/// using this mode, as it allows the potential to permanently modify data.
/// This mode is available only if dependency checking is turned off in
/// the AbstractDependencyInjectionSpringContextTests superclass. The non-transactional
/// capability is provided to enable use of the same subclass in different environments.</i></p>
///
/// </remarks>
///
/// <author>Rod Johnson</author>
/// <author>Juergen Hoeller</author>
/// <author>Rick Evans</author>
/// <author>Mark Pollack (.NET)</author>
public abstract class AbstractTransactionalSpringContextTests : AbstractDependencyInjectionSpringContextTests
{
/// <summary>
/// The transaction manager to use
/// </summary>
protected IPlatformTransactionManager transactionManager;
/// <summary>
/// Should we roll back by default?
/// </summary>
private bool defaultRollback = true;
/// <summary>
/// Should we commit the current transaction?
/// </summary>
private bool complete = false;
/// <summary>
/// Number of transactions started
/// </summary>
private int transactionsStarted = 0;
/// <summary>
/// Default transaction definition is used.
/// Subclasses can change this to cause different behaviour.
/// </summary>
private ITransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
/// <summary>
/// TransactionStatus for this test. Typical subclasses won't need to use it.
/// </summary>
protected ITransactionStatus transactionStatus;
/// <summary>
/// Initializes a new instance of the <see cref="AbstractTransactionalSpringContextTests"/> class.
/// </summary>
public AbstractTransactionalSpringContextTests()
{
}
/// <summary>
/// Sets the transaction manager to use.
/// </summary>
public IPlatformTransactionManager TransactionManager
{
set { transactionManager = value; }
}
/// <summary>
/// Sets the default rollback flag.
/// </summary>
public bool DefaultRollback
{
set { defaultRollback = value; }
}
/// <summary>
/// Set the <see cref="ITransactionDefinition"/> to be used
/// </summary>
/// <remarks>
/// Defaults to <see cref="DefaultTransactionDefinition"/>
/// </remarks>
protected ITransactionDefinition TransactionDefinition
{
set { transactionDefinition = value; }
}
/// <summary>
/// Prevents the transaction.
/// </summary>
protected virtual void PreventTransaction()
{
this.transactionDefinition = null;
}
/// <summary>
/// Creates a transaction
/// </summary>
protected override void OnSetUp()
{
this.complete = !this.defaultRollback;
if (this.transactionManager == null)
{
logger.Info("No transaction manager set: test will NOT run within a transaction");
}
else if (this.transactionDefinition == null)
{
logger.Info("No transaction definition set: test will NOT run within a transaction");
}
else
{
OnSetUpBeforeTransaction();
StartNewTransaction();
try
{
OnSetUpInTransaction();
}
catch (Exception)
{
EndTransaction();
throw;
}
}
}
/// <summary>
/// Callback method called before transaction is setup.
/// </summary>
protected virtual void OnSetUpBeforeTransaction()
{
}
/// <summary>
/// Callback method called after transaction is setup.
/// </summary>
protected virtual void OnSetUpInTransaction()
{
}
/// <summary>
/// rollback the transaction.
/// </summary>
protected override void OnTearDown()
{
// Call onTearDownInTransaction and end transaction if the transaction is still active.
if (this.transactionStatus != null && !this.transactionStatus.Completed)
{
try
{
OnTearDownInTransaction();
}
finally
{
EndTransaction();
}
}
// Call onTearDownAfterTransaction if there was at least one transaction,
// even if it has been completed early through an endTransaction() call.
if (this.transactionsStarted > 0)
{
OnTearDownAfterTransaction();
}
}
/// <summary>
/// Callback before rolling back the transaction.
/// </summary>
protected virtual void OnTearDownInTransaction()
{
}
/// <summary>
/// Callback after rolling back the transaction.
/// </summary>
protected virtual void OnTearDownAfterTransaction()
{
}
/// <summary>
/// Set the complete flag..
/// </summary>
protected virtual void SetComplete()
{
if (this.transactionManager == null)
{
throw new InvalidOperationException("No transaction manager set");
}
this.complete = true;
}
/// <summary>
/// Ends the transaction.
/// </summary>
protected virtual void EndTransaction()
{
if (this.transactionStatus != null)
{
try
{
if (!this.complete)
{
this.transactionManager.Rollback(this.transactionStatus);
logger.Info("Rolled back transaction after test execution");
}
else
{
this.transactionManager.Commit(this.transactionStatus);
logger.Info("Committed transaction after test execution");
}
}
finally
{
this.transactionStatus = null;
}
}
}
/// <summary>
/// Starts the new transaction.
/// </summary>
protected void StartNewTransaction()
{
if (this.transactionStatus != null)
{
throw new InvalidOperationException("Cannot start new transaction without ending existing transaction: " +
"Invoke endTransaction() before startNewTransaction()");
}
if (this.transactionManager == null)
{
throw new InvalidOperationException("No transaction manager set");
}
this.transactionStatus = this.transactionManager.GetTransaction(this.transactionDefinition);
++this.transactionsStarted;
this.complete = !this.defaultRollback;
if (logger.IsInfoEnabled)
{
logger.Info("Began transaction (" + this.transactionsStarted + "): transaction manager [" +
this.transactionManager + "]; default rollback = " + this.defaultRollback);
}
}
}
}
|