The Reusable Test Scope Pattern
I’m almost positive someone has come up with this pattern before, but it was a big help on a recent project so I thought I’d share it. The basic goal of the pattern is to make the setup and teardown work of test classes reusable. We found this to cause particular pain for integration tests written against a database where multiple dependent records needed to be created before the actual test could be performed. The second goal was that these externalized bits of code would be composable so that tests requiring multiple setup resources could create just the right ones.
Before really describing the workflow of the pattern, here are the basic components of our implementation:
The ITestScope Interface
public interface ITestScope { void SetupScope(); void TeardownScope(); }
These two methods (SetupScope and TeardownScope) are where we move all of the test setup and cleanup logic. Any state that needs to be maintained between setup and teardown (i.e. needs to be available to the test itself) can be stored in public properties or fields on the class implementing ITestScope.
The TestBase Class
public abstract class TestBase { public abstract void ScopeSetup(); public abstract void ScopeTeardown(); }
Requiring that all test classes inherit from TestBase insures that at least developers will be considering the setup and cleanup of their tests. The TestBase.ScopeSetup method would include instantiating and calling the TestScope.SetupScope methods for any scopes that are needed in the test class…likewise for teardown.
The TestFactory Class
public static class TestFactory { public static TRepository GetRepository<TRepository>() where TRepository : class {} public static TController GetController<TController>() where TController : Controller {} }
This isn’t directly related to the scope pattern, but it’s invaluable as a point where you can use a DI container in the test project. All repository or controllers (or other resources) can be obtained from here.
An Example
So here’s an example test class that might use this pattern.
public class CustomerTestScope : ITestScope { private ICustomerRepository _customerRepo; public Customer TestCustomer { get; private set; } public CustomerTestScope(ICustomerRepository customerRepo) { _customerRepo = customerRepo; } public void SetupScope() { TestCustomer = _customerRepo.Create(); // Other complex test setup logic } public void TeardownScope() { _customerRepo.Delete(TestCustomer); // The reverse of the complex setup logic } } [TestClass] public class CustomerRepositoryTests : TestBase { private CustomerTestScope _customerScope; private ICustomerRepository _customerRepo; [TestInitialize] public override void ScopeSetup() { _customerRepo = TestFactory.GetRepository<ICustomerRepository>(); _customerScope = new CustomerTestScope(_customerRepo); _customerScope.SetupScope(); } [TestMethod] public void SaveCustomer_ShouldRenameCustomer_WhenNewNameGiven() { _customerScope.TestCustomer.Name = "New Name"; _customerRepo.Save(_customerScope.TestCustomer); var customer = _customerRepo.GetById(_customerScope.TestCustomer.ID); Assert.IsTrue(customer.Name == "New Name", "SaveCustomer failed to update customer name."); } [TestCleanup] public override void ScopeTeardown() { _customerScope.TeardownScope(); } }
If additional scopes were needed then they could simple be added to the test class. For example, if a ProductTestScope was also needed then it would be initialized just like the CustomerTestScope and torn down appropriately.
Dependencies between scopes would be supplied via constructor parameters. This does imply that the SetupScope methods be called in sequence and TeardownScope be called in reverse order.
That’s the pattern, pretty simple but now the logic for creating these setup conditions can be offloaded and reused in a simple way and we’ve made it easy to clean up that test data after the tests complete.