From ed120b2f52f3b15b58b8c81b66422ee20db9b9d0 Mon Sep 17 00:00:00 2001 From: markpollack Date: Tue, 21 Apr 2009 05:36:10 +0000 Subject: [PATCH] Add support for Microsoft's testing framework --- .../Spring.Testing.Microsoft/AssemblyInfo.cs | 5 + .../Spring.Testing.Microsoft.2008.csproj | 115 ++++++ ...ctDependencyInjectionSpringContextTests.cs | 370 ++++++++++++++++++ .../Microsoft/AbstractSpringContextTests.cs | 238 +++++++++++ ...ansactionalDbProviderSpringContextTests.cs | 125 ++++++ ...AbstractTransactionalSpringContextTests.cs | 306 +++++++++++++++ .../Spring.Testing.NUnit/AssemblyInfo.cs | 2 +- .../AssemblyInfo.cs | 10 + ...Spring.Testing.Microsoft.Tests.2008.csproj | 87 ++++ .../Spring.Testing.Microsoft.Tests.dll.config | 46 +++ ...endencyInjectionSpringContextTestsTests.cs | 130 ++++++ .../Microsoft/TestApplicationContext.xml | 19 + .../Spring.Testing.Microsoft.Tests/app.config | 31 ++ ...endencyInjectionSpringContextTestsTests.cs | 21 +- 14 files changed, 1503 insertions(+), 2 deletions(-) create mode 100644 src/Spring/Spring.Testing.Microsoft/AssemblyInfo.cs create mode 100644 src/Spring/Spring.Testing.Microsoft/Spring.Testing.Microsoft.2008.csproj create mode 100644 src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractDependencyInjectionSpringContextTests.cs create mode 100644 src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractSpringContextTests.cs create mode 100644 src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalDbProviderSpringContextTests.cs create mode 100644 src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalSpringContextTests.cs create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/AssemblyInfo.cs create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.2008.csproj create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.dll.config create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/AbstractDependencyInjectionSpringContextTestsTests.cs create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/TestApplicationContext.xml create mode 100644 test/Spring/Spring.Testing.Microsoft.Tests/app.config diff --git a/src/Spring/Spring.Testing.Microsoft/AssemblyInfo.cs b/src/Spring/Spring.Testing.Microsoft/AssemblyInfo.cs new file mode 100644 index 00000000..1fa90498 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: AssemblyTitle("Spring.Testing.Microsoft")] +[assembly: AssemblyDescription("Interfaces and classes that provide Microsoft Unit Testing integration in Spring.Net")] \ No newline at end of file diff --git a/src/Spring/Spring.Testing.Microsoft/Spring.Testing.Microsoft.2008.csproj b/src/Spring/Spring.Testing.Microsoft/Spring.Testing.Microsoft.2008.csproj new file mode 100644 index 00000000..98da2659 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/Spring.Testing.Microsoft.2008.csproj @@ -0,0 +1,115 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {2E10A8D3-B8FF-4699-90B8-E621659C58DD} + Library + Properties + Spring + Spring.Testing.Microsoft + + + 2.0 + + + http://localhost/Spring.Testing.Microsoft/ + true + Web + true + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + true + false + true + + + true + full + false + ..\..\..\build\VS.NET.2005\Spring.Testing.Microsoft\Debug\ + TRACE;DEBUG;NET_2_0 + prompt + 4 + ..\..\..\build\VS.NET.2005\Spring.Testing.Microsoft\Debug\Spring.Testing.Microsoft.xml + + + pdbonly + true + ..\..\..\build\VS.NET.2005\Spring.Testing.Microsoft\Release\ + TRACE;NET_2_0 + prompt + 4 + + + + False + ..\..\..\lib\net\2.0\Common.Logging.dll + + + + + + + + CommonAssemblyInfo.cs + + + + + + + + + + False + .NET Framework Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + false + + + + + {710961A3-0DF4-49E4-A26E-F5B9C044AC84} + Spring.Core.2008 + + + {AE00E5AB-C39A-436F-86D2-33BFE33E2E40} + Spring.Data.2008 + + + + + \ No newline at end of file diff --git a/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractDependencyInjectionSpringContextTests.cs b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractDependencyInjectionSpringContextTests.cs new file mode 100644 index 00000000..372eabd2 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractDependencyInjectionSpringContextTests.cs @@ -0,0 +1,370 @@ +#region License + +/* +/// Copyright 2002-2006 the original author or authors. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + */ + +#endregion + +using System; +using System.Collections; +using System.Reflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Spring.Context; +using Spring.Context.Support; +using Spring.Objects.Factory; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Testing.Microsoft +{ + /// + /// Convenient superclass for tests depending on a Spring context. + /// The test instance itself is populated by Dependency Injection. + /// + /// + /// + ///

Really for integration testing, not unit testing. + /// You should not normally use the Spring container + /// for unit tests: simply populate your objects in plain NUnit tests!

+ /// + ///

This supports two modes of populating the test: + ///

+ /// + ///

This class will normally cache contexts based on a context key: + /// normally the config locations String array describing the Spring resource + /// descriptors making up the context. Unless the SetDirty() method + /// is called by a test, the context will not be reloaded, even across different + /// subclasses of this test. This is particularly beneficial if your context is + /// slow to construct, for example if you are using Hibernate and the time taken + /// to load the mappings is an issue.

+ /// + ///

If you don't want this behavior, you can override the ContextKey + /// property, most likely to return the test class. In conjunction with this you would + /// probably override the GetContext method, which by default loads + /// the locations specified by the ConfigLocations property.

+ /// + ///

WARNING: When doing integration tests from within VS.NET, only use + /// assembly resource URLs. Else, you may see misleading failures when changing + /// context locations.

+ ///
+ /// + /// Rod Johnson + /// Rob Harrop + /// Rick Evans + /// Aleksandar Seovic (.NET) + public abstract class AbstractDependencyInjectionSpringContextTests : AbstractSpringContextTests + { + private bool populateProtectedVariables = false; + private AutoWiringMode autowireMode = AutoWiringMode.ByType; + private bool dependencyCheck = true; + + /// + /// Application context this test will run against. + /// + protected IConfigurableApplicationContext applicationContext; + + /// + /// Holds names of the fields that should be used for field injection. + /// + protected string[] managedVariableNames; + private int loadCount = 0; + + /// + /// Default constructor for AbstractDependencyInjectionSpringContextTests. + /// + public AbstractDependencyInjectionSpringContextTests() + {} + + /// + /// Gets or sets a flag specifying whether to populate protected + /// variables of this test case. + /// + /// + /// A flag specifying whether to populate protected variables of this test case. + /// Default is false. + /// + public bool PopulateProtectedVariables + { + get { return populateProtectedVariables; } + set { populateProtectedVariables = value; } + } + + /// + /// Gets or sets the autowire mode for test properties set by Dependency Injection. + /// + /// + /// The autowire mode for test properties set by Dependency Injection. + /// The default is . + /// + public AutoWiringMode AutowireMode + { + get { return autowireMode; } + set { autowireMode = value; } + } + + /// + /// Gets or sets a flag specifying whether or not dependency checking + /// should be performed for test properties set by Dependency Injection. + /// + /// + ///

A flag specifying whether or not dependency checking + /// should be performed for test properties set by Dependency Injection.

+ ///

The default is true, meaning that tests cannot be run + /// unless all properties are populated.

+ ///
+ public bool DependencyCheck + { + get { return dependencyCheck; } + set { dependencyCheck = value; } + } + + /// + /// Gets the current number of context load attempts. + /// + public int LoadCount + { + get { return loadCount; } + } + + /// + /// Called to say that the "applicationContext" instance variable is dirty and + /// should be reloaded. We need to do this if a test has modified the context + /// (for example, by replacing an object definition). + /// + public void SetDirty() + { + SetDirty(ConfigLocations); + } + + /// + /// Test setup method. + /// + [TestInitialize] + public virtual void TestInitialize() + { + this.applicationContext = GetContext(ContextKey); + InjectDependencies(); + try + { + OnTestInitialize(); + } + catch (Exception ex) + { + logger.Error("Setup error", ex); + throw; + } + } + + /// + /// Inject dependencies into 'this' instance (that is, this test instance). + /// + /// + ///

The default implementation populates protected variables if the + /// property is set, else + /// uses autowiring if autowiring is switched on (which it is by default).

+ ///

You can certainly override this method if you want to totally control + /// how dependencies are injected into 'this' instance.

+ ///
+ protected virtual void InjectDependencies() + { + if (PopulateProtectedVariables) + { + if (this.managedVariableNames == null) + { + InitManagedVariableNames(); + } + InjectProtectedVariables(); + } + else if (AutowireMode != AutoWiringMode.No) + { + IConfigurableListableObjectFactory factory = this.applicationContext.ObjectFactory; + ((AbstractObjectFactory) factory).IgnoreDependencyType(typeof(AutoWiringMode)); + factory.AutowireObjectProperties(this, AutowireMode, DependencyCheck); + } + } + + /// + /// Gets a key for this context. Usually based on config locations, but + /// a subclass overriding buildContext() might want to return its class. + /// + protected virtual object ContextKey + { + get { return ConfigLocations; } + } + + /// + /// Loads application context from the specified resource locations. + /// + /// Resources to load object definitions from. + protected override IConfigurableApplicationContext LoadContextLocations(string[] locations) + { + ++this.loadCount; + return base.LoadContextLocations(locations); + } + + /// + /// Retrieves the names of the fields that should be used for field injection. + /// + protected virtual void InitManagedVariableNames() + { + ArrayList managedVarNames = new ArrayList(); + Type type = GetType(); + + do + { + FieldInfo[] fields = + type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Instance); + if (logger.IsDebugEnabled) + { + logger.Debug("Found " + fields.Length + " fields on " + type); + } + + for (int i = 0; i < fields.Length; i++) + { + FieldInfo field = fields[i]; + if (logger.IsDebugEnabled) + { + logger.Debug("Candidate field: " + field); + } + if (IsProtectedInstanceField(field)) + { + object oldValue = field.GetValue(this); + if (oldValue == null) + { + managedVarNames.Add(field.Name); + if (logger.IsDebugEnabled) + { + logger.Debug("Added managed variable '" + field.Name + "'"); + } + } + else + { + if (logger.IsDebugEnabled) + { + logger.Debug("Rejected managed variable '" + field.Name + "'"); + } + } + } + } + type = type.BaseType; + } while (type != typeof (AbstractDependencyInjectionSpringContextTests)); + + this.managedVariableNames = (string[]) managedVarNames.ToArray(typeof (string)); + } + + private static bool IsProtectedInstanceField(FieldInfo field) + { + return field.IsFamily; + } + + /// + /// Injects protected fields using Field Injection. + /// + protected virtual void InjectProtectedVariables() + { + for (int i = 0; i < this.managedVariableNames.Length; i++) + { + string fieldName = this.managedVariableNames[i]; + Object obj = null; + try + { + FieldInfo field = GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (field != null) + { + BeforeProtectedVariableInjection(field); + obj = this.applicationContext.GetObject(fieldName, field.FieldType); + field.SetValue(this, obj); + if (logger.IsDebugEnabled) + { + logger.Debug("Populated field: " + field); + } + } + else + { + if (logger.IsWarnEnabled) + { + logger.Warn("No field with name '" + fieldName + "'"); + } + } + } + catch (NoSuchObjectDefinitionException) + { + if (logger.IsWarnEnabled) + { + logger.Warn("No object definition with name '" + fieldName + "'"); + } + } + } + } + + /// + /// Called right before a field is being injected + /// + protected virtual void BeforeProtectedVariableInjection(FieldInfo fieldInfo) + { + + } + + /// + /// Subclasses can override this method in order to + /// add custom test setup logic. + /// + protected virtual void OnTestInitialize() + {} + + /// + /// Test teardown method. + /// + [TestCleanup] + public void TestCleanup() + { + try + { + OnTestCleanup(); + } + catch (Exception ex) + { + logger.Error("OnTearDown error", ex); + } + } + + /// + /// Subclasses can override this method in order to + /// add custom test teardown logic. + /// + protected virtual void OnTestCleanup() + {} + + + /// + /// Subclasses must implement this property to return the locations of their + /// config files. A plain path will be treated as a file system location. + /// + /// An array of config locations + protected abstract string[] ConfigLocations { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractSpringContextTests.cs b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractSpringContextTests.cs new file mode 100644 index 00000000..9c86e6d6 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractSpringContextTests.cs @@ -0,0 +1,238 @@ +#region License + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections; +using Common.Logging; +using Spring.Context; +using Spring.Context.Support; +using Spring.Util; + +namespace Spring.Testing.Microsoft +{ + /// + /// Superclass for NUnit test cases using a Spring context. + /// + /// + ///

Maintains a cache of contexts by key. This has significant performance + /// benefit if initializing the context would take time. While initializing a + /// Spring context itself is very quick, some objects in a context, such as + /// a LocalSessionFactoryObject for working with NHibernate, may take time to + /// initialize. Hence it often makes sense to do that initializing once.

+ ///

Normally you won't extend this class directly but rather extend one + /// of its subclasses.

+ ///
+ /// Rod Johnson + /// Aleksandar Seovic (.NET) + public abstract class AbstractSpringContextTests + { + /// + /// Map of context keys returned by subclasses of this class, to + /// Spring contexts. + /// + private static readonly IDictionary contextKeyToContextMap; + + /// + /// Static ctor to avoid "beforeFieldInit" problem. + /// + static AbstractSpringContextTests() + { + contextKeyToContextMap = new Hashtable(); + } + + /// + /// Disposes any cached context instance and removes it from cache. + /// + public static void ClearContextCache() + { + foreach(IApplicationContext ctx in contextKeyToContextMap.Values) + { + ctx.Dispose(); + } + contextKeyToContextMap.Clear(); + } + + /// + /// Indicates, whether context instances should be automatically registered with the global . + /// + private bool registerContextWithContextRegistry = true; + + /// + /// Logger available to subclasses. + /// + protected readonly ILog logger; + + /// + /// Default constructor for AbstractSpringContextTests. + /// + protected AbstractSpringContextTests() + { + logger = LogManager.GetLogger(GetType()); + } + + /// + /// Controls, whether application context instances will + /// be registered/unregistered with the global . + /// Defaults to true. + /// + public bool RegisterContextWithContextRegistry + { + get { return registerContextWithContextRegistry; } + set { registerContextWithContextRegistry = value; } + } + + /// + /// Set custom locations dirty. This will cause them to be reloaded + /// from the cache before the next test case is executed. + /// + /// + /// Call this method only if you change the state of a singleton + /// object, potentially affecting future tests. + /// + /// Locations + protected void SetDirty(string[] locations) + { + String keyString = ContextKeyString(locations); + IConfigurableApplicationContext ctx = + (IConfigurableApplicationContext) contextKeyToContextMap[keyString]; + contextKeyToContextMap.Remove(keyString); + + if (ctx != null) + { + ctx.Dispose(); + } + } + + /// + /// Returns true if context for the specified + /// is cached. + /// + /// Context key to check. + /// + /// true if context for the specified + /// is cached, + /// false otherwise. + /// + protected bool HasCachedContext(object contextKey) + { + string keyString = ContextKeyString(contextKey); + return contextKeyToContextMap.Contains(keyString); + } + + /// + /// Converts context key to string. + /// + /// + /// Subclasses can override this to return a string representation of + /// their contextKey for use in logging. + /// + /// Context key to convert. + /// + /// String representation of the specified . Null if + /// contextKey is null. + /// + protected virtual string ContextKeyString(object contextKey) + { + if (contextKey == null) + { + return null; + } + if (contextKey is string[]) + { + return StringUtils.ArrayToCommaDelimitedString((string[]) contextKey); + } + else + { + return contextKey.ToString(); + } + } + + /// + /// Caches application context. + /// + /// Key to use. + /// Context to cache. + public void AddContext(object key, IConfigurableApplicationContext context) + { + AssertUtils.ArgumentNotNull(context, "context", "ApplicationContext must not be null"); + string keyString = ContextKeyString(key); + contextKeyToContextMap.Add(keyString, context); + + if (RegisterContextWithContextRegistry + && !ContextRegistry.IsContextRegistered(context.Name)) + { + ContextRegistry.RegisterContext(context); + } + } + + /// + /// Returns cached context if present, or loads it if not. + /// + /// Context key. + /// Spring application context associated with the specified key. + protected IConfigurableApplicationContext GetContext(object key) + { + string keyString = ContextKeyString(key); + IConfigurableApplicationContext ctx = + (IConfigurableApplicationContext) contextKeyToContextMap[keyString]; + if (ctx == null) + { + if (key is string[]) + { + ctx = LoadContextLocations((string[]) key); + } + else + { + ctx = LoadContext(key); + } + AddContext(key, ctx); + } + return ctx; + } + + + /// + /// Loads application context from the specified resource locations. + /// + /// Resources to load object definitions from. + protected virtual IConfigurableApplicationContext LoadContextLocations(string[] locations) + { + if (logger.IsInfoEnabled) + { + logger.Info("Loading config for: " + StringUtils.ArrayToCommaDelimitedString(locations)); + } + return new XmlApplicationContext(locations); + } + + /// + /// Loads application context based on user-defined key. + /// + /// + /// Unless overriden by the user, this method will alway throw + /// a . + /// + /// User-defined key. + protected virtual IConfigurableApplicationContext LoadContext(object key) + { + throw new NotSupportedException("Subclasses may override this"); + } + + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalDbProviderSpringContextTests.cs b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalDbProviderSpringContextTests.cs new file mode 100644 index 00000000..eb2e32d8 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalDbProviderSpringContextTests.cs @@ -0,0 +1,125 @@ +#region License + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + + +using System; +using System.Data; +using Spring.Data.Common; +using Spring.Data.Core; + +namespace Spring.Testing.Microsoft +{ + /// + /// Subclass of AbstractTransactionalSpringContextTests that adds some convenience + /// functionality for ADO.NET access. Expects a IDbProvider object + /// to be defined in the Spring application context. + /// + /// + /// This class exposes a AdoTemplate and provides an easy way to delete from the + /// database in a new transaction. + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + public abstract class AbstractTransactionalDbProviderSpringContextTests : AbstractTransactionalSpringContextTests + { + /// + /// Holds the that this base class manages + /// + protected AdoTemplate adoTemplate; + + /// + /// Did this test delete any tables? If so, we forbid transaction completion, + /// and only allow rollback. + /// + private bool zappedTables; + + /// + /// Initializes a new instance of the class. + /// + public AbstractTransactionalDbProviderSpringContextTests() + { + } + + /// + /// Sets the DbProvider, via Dependency Injection. + /// + /// The IDbProvider. + public IDbProvider DbProvider + { + set { adoTemplate = new AdoTemplate(value); } + } + + /// + /// Gets or sets the AdoTemplate that this base class manages. + /// + /// The ADO template. + public AdoTemplate AdoTemplate + { + get { return adoTemplate; } + } + + /// + /// Convenient method to delete all rows from these tables. + /// Calling this method will make avoidance of rollback by calling + /// SetComplete() impossible. + /// + /// + protected void DeleteFromTables(String[] names) + { + for (int i = 0; i < names.Length; i++) + { + int rowCount = this.adoTemplate.ExecuteNonQuery(CommandType.Text, "DELETE FROM " + names[i]); + if (logger.IsInfoEnabled) + { + logger.Info("Deleted " + rowCount + " rows from table " + names[i]); + } + } + this.zappedTables = true; + } + + /// + /// Overridden to prevent the transaction committing if a number of tables have been + /// cleared, as a defensive measure against accidental permanent wiping of a database. + /// + protected override void SetComplete() + { + if (this.zappedTables) + { + throw new InvalidOperationException("Cannot set complete after deleting tables"); + } + base.SetComplete(); + } + + /// + /// Counts the rows in given table. + /// + /// Name of the table to count rows in. + /// The number of rows in the table + protected int CountRowsInTable(String tableName) + { + return (int) adoTemplate.ExecuteScalar(CommandType.Text, "SELECT COUNT(0) FROM " + tableName); + + } + + //TODO ExecuteScript... + + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalSpringContextTests.cs b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalSpringContextTests.cs new file mode 100644 index 00000000..2ecbbd82 --- /dev/null +++ b/src/Spring/Spring.Testing.Microsoft/Testing/Microsoft/AbstractTransactionalSpringContextTests.cs @@ -0,0 +1,306 @@ +#region License + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + + +using System; +using Spring.Transaction; +using Spring.Transaction.Support; + +namespace Spring.Testing.Microsoft +{ + /// + /// Convenient superclass for tests that should occur in a transaction, but normally + /// will roll the transaction back on the completion of each test. + /// + /// + ///

This is useful in a range of circumstances, allowing the following benefits:

+ /// + /// + ///

This class is typically very fast, compared to traditional setup/teardown scripts.

+ /// + ///

If data should be left in the database, call the SetComplete() + /// method in each test. The "DefaultRollback" property, which defaults to "true", + /// determines whether transactions will complete by default.

+ /// + ///

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 + /// endTransaction() method. Execution will then occur without a + /// transactional context.

+ /// + ///

The StartNewTransaction() method may be called after a call to + /// EndTransaction() 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 setComplete() 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.

+ /// + ///

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.

+ /// + ///

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.

+ /// + ///
+ /// + /// Rod Johnson + /// Juergen Hoeller + /// Rick Evans + /// Mark Pollack (.NET) + public abstract class AbstractTransactionalSpringContextTests : AbstractDependencyInjectionSpringContextTests + { + /// + /// The transaction manager to use + /// + protected IPlatformTransactionManager transactionManager; + + /// + /// Should we roll back by default? + /// + private bool defaultRollback = true; + + /// + /// Should we commit the current transaction? + /// + private bool complete = false; + + /// + /// Number of transactions started + /// + private int transactionsStarted = 0; + + /// + /// Default transaction definition is used. + /// Subclasses can change this to cause different behaviour. + /// + private ITransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + + /// + /// TransactionStatus for this test. Typical subclasses won't need to use it. + /// + protected ITransactionStatus transactionStatus; + + + /// + /// Initializes a new instance of the class. + /// + public AbstractTransactionalSpringContextTests() + { + } + + /// + /// Sets the transaction manager to use. + /// + public IPlatformTransactionManager TransactionManager + { + set { transactionManager = value; } + } + + /// + /// Sets the default rollback flag. + /// + public bool DefaultRollback + { + set { defaultRollback = value; } + } + + /// + /// Set the to be used + /// + /// + /// Defaults to + /// + protected ITransactionDefinition TransactionDefinition + { + set { transactionDefinition = value; } + } + + /// + /// Prevents the transaction. + /// + protected virtual void PreventTransaction() + { + this.transactionDefinition = null; + } + + + /// + /// Creates a transaction + /// + protected override void OnTestInitialize() + { + 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; + } + } + } + + + /// + /// Callback method called before transaction is setup. + /// + protected virtual void OnSetUpBeforeTransaction() + { + } + + /// + /// Callback method called after transaction is setup. + /// + protected virtual void OnSetUpInTransaction() + { + } + + /// + /// rollback the transaction. + /// + protected override void OnTestCleanup() + { + // 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(); + } + } + + /// + /// Callback before rolling back the transaction. + /// + protected virtual void OnTearDownInTransaction() + { + } + + /// + /// Callback after rolling back the transaction. + /// + protected virtual void OnTearDownAfterTransaction() + { + } + + /// + /// Set the complete flag.. + /// + protected virtual void SetComplete() + { + if (this.transactionManager == null) + { + throw new InvalidOperationException("No transaction manager set"); + } + this.complete = true; + } + + /// + /// Ends the transaction. + /// + 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; + } + } + } + + /// + /// Starts the new transaction. + /// + 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); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Testing.NUnit/AssemblyInfo.cs b/src/Spring/Spring.Testing.NUnit/AssemblyInfo.cs index 074c740a..03bb47d6 100644 --- a/src/Spring/Spring.Testing.NUnit/AssemblyInfo.cs +++ b/src/Spring/Spring.Testing.NUnit/AssemblyInfo.cs @@ -1,5 +1,5 @@ using System; using System.Reflection; -[assembly: AssemblyTitle("Spring.Net NUnit support")] +[assembly: AssemblyTitle("Spring.Testing.NUnit")] [assembly: AssemblyDescription("Interfaces and classes that provide NUnit integration in Spring.Net")] \ No newline at end of file diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/AssemblyInfo.cs b/test/Spring/Spring.Testing.Microsoft.Tests/AssemblyInfo.cs new file mode 100644 index 00000000..c388fe56 --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("Spring.Testing.Microsoft Tests")] +[assembly: AssemblyDescription("Unit tests for Spring.Testing.Microsoft assembly")] \ No newline at end of file diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.2008.csproj b/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.2008.csproj new file mode 100644 index 00000000..1a9769d3 --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.2008.csproj @@ -0,0 +1,87 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {7321149A-47C6-4FBA-9D1A-26FD6815381B} + Library + Properties + Spring + Spring.Testing.Microsoft.Tests + v2.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\..\lib\Net\2.0\Common.Logging.dll + + + + + + + + + + + + + + + + + + {710961A3-0DF4-49E4-A26E-F5B9C044AC84} + Spring.Core.2008 + + + {AE00E5AB-C39A-436F-86D2-33BFE33E2E40} + Spring.Data.2008 + + + {2E10A8D3-B8FF-4699-90B8-E621659C58DD} + Spring.Testing.Microsoft.2008 + + + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904} + Spring.Core.Tests.2008 + + + {ACD39D47-1811-40FA-9E7E-5DEA5B9CE6C0} + Spring.Data.Tests.2008 + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.dll.config b/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.dll.config new file mode 100644 index 00000000..0cfa9c7b --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft.Tests.dll.config @@ -0,0 +1,46 @@ + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/AbstractDependencyInjectionSpringContextTestsTests.cs b/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/AbstractDependencyInjectionSpringContextTestsTests.cs new file mode 100644 index 00000000..9257c54e --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/AbstractDependencyInjectionSpringContextTestsTests.cs @@ -0,0 +1,130 @@ +#region License + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Spring.Context; +using Spring.Context.Support; + +namespace Spring.Testing.Microsoft +{ + /// + /// Summary description for AbstractDependencyInjectionSpringContextTestsTests. + /// + [TestClass] + public class AbstractDependencyInjectionSpringContextTestsTests + { + private class TestAbstractDependencyInjectionSpringContextTests :AbstractDependencyInjectionSpringContextTests + { + public static readonly string[] CONFIGLOCATIONS = new string[] {"assembly://Spring.Testing.Microsoft.Tests/Spring.Testing.Microsoft/TestApplicationContext.xml"}; + + public TestAbstractDependencyInjectionSpringContextTests() + {} + + public TestAbstractDependencyInjectionSpringContextTests(bool registerWithContextRegistry) + { + base.RegisterContextWithContextRegistry = registerWithContextRegistry; + } + + public IConfigurableApplicationContext ApplicationContext + { + get { return base.applicationContext; } + } + + protected override string[] ConfigLocations + { + get { return CONFIGLOCATIONS; } + } + + public new bool HasCachedContext(object key) + { + return base.HasCachedContext(key); + } + } + + private TestAbstractDependencyInjectionSpringContextTests fixtureInstance ; + + [TestCleanup] + public void TearDown() + { + AbstractSpringContextTests.ClearContextCache(); + } + + [TestMethod] + public void RegistersWithContextRegistryByDefault() + { + fixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(); + Assert.IsTrue(fixtureInstance.RegisterContextWithContextRegistry); + } + + [TestMethod] + public void UnregistersFromContextRegistryWhenDirty() + { + fixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(); + Assert.IsTrue(fixtureInstance.RegisterContextWithContextRegistry); + fixtureInstance.TestInitialize(); + Assert.IsTrue( ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name) ); + fixtureInstance.TestCleanup(); + Assert.IsTrue(ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name)); + fixtureInstance.TestInitialize(); + Assert.IsTrue(ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name)); + fixtureInstance.SetDirty(); + fixtureInstance.TestCleanup(); + Assert.IsFalse(ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name)); + } + + [TestMethod] + public void DoesNotRegisterContextWithContextRegistry() + { + fixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(); + Assert.IsTrue(fixtureInstance.RegisterContextWithContextRegistry); + fixtureInstance.RegisterContextWithContextRegistry = false; + fixtureInstance.TestInitialize(); + Assert.IsFalse( ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name) ); + fixtureInstance.TestCleanup(); + Assert.IsFalse( ContextRegistry.IsContextRegistered(fixtureInstance.ApplicationContext.Name) ); + } + + [TestMethod] + public void CachesApplicationContexts() + { + fixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(false); + fixtureInstance.TestInitialize(); + Assert.IsNotNull(fixtureInstance.ApplicationContext); + Assert.AreEqual(1, fixtureInstance.LoadCount); // context has been loaded + Assert.IsTrue(fixtureInstance.HasCachedContext(TestAbstractDependencyInjectionSpringContextTests.CONFIGLOCATIONS)); + fixtureInstance.TestCleanup(); + + TestAbstractDependencyInjectionSpringContextTests otherFixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(false); + otherFixtureInstance.TestInitialize(); + Assert.IsNotNull(otherFixtureInstance.ApplicationContext); + Assert.AreEqual(0, otherFixtureInstance.LoadCount); // context was obtained from cache + Assert.AreSame(fixtureInstance.ApplicationContext, otherFixtureInstance.ApplicationContext); + otherFixtureInstance.SetDirty(); // purge cache and dispose cached instances + Assert.IsFalse(fixtureInstance.HasCachedContext(TestAbstractDependencyInjectionSpringContextTests.CONFIGLOCATIONS)); + otherFixtureInstance.TestCleanup(); + otherFixtureInstance = new TestAbstractDependencyInjectionSpringContextTests(false); + otherFixtureInstance.TestInitialize(); + Assert.IsNotNull(otherFixtureInstance.ApplicationContext); + Assert.AreEqual(1, otherFixtureInstance.LoadCount); // context was reloaded because of SetDirty() above + Assert.AreNotSame(fixtureInstance.ApplicationContext, otherFixtureInstance.ApplicationContext); + otherFixtureInstance.TestCleanup(); + } + } +} diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/TestApplicationContext.xml b/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/TestApplicationContext.xml new file mode 100644 index 00000000..a5702f11 --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/Testing/Microsoft/TestApplicationContext.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.Testing.Microsoft.Tests/app.config b/test/Spring/Spring.Testing.Microsoft.Tests/app.config new file mode 100644 index 00000000..83ff1365 --- /dev/null +++ b/test/Spring/Spring.Testing.Microsoft.Tests/app.config @@ -0,0 +1,31 @@ + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Testing.NUnit.Tests/Testing/NUnit/AbstractDependencyInjectionSpringContextTestsTests.cs b/test/Spring/Spring.Testing.NUnit.Tests/Testing/NUnit/AbstractDependencyInjectionSpringContextTestsTests.cs index 9a9199c3..89642ced 100644 --- a/test/Spring/Spring.Testing.NUnit.Tests/Testing/NUnit/AbstractDependencyInjectionSpringContextTestsTests.cs +++ b/test/Spring/Spring.Testing.NUnit.Tests/Testing/NUnit/AbstractDependencyInjectionSpringContextTestsTests.cs @@ -1,4 +1,23 @@ -using System; +#region License + +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + using NUnit.Framework; using Spring.Context; using Spring.Context.Support;