When writing code, I often end up introducing a layer supertype – i.e. a base class with functionality shared by all implementations in that particular layer in my application.
This also holds for my test code – and why shouldn’t it? Test code is as real as real code, so the same rules apply and it should benefit from the same pain killers as we implement in our application code.
For example when testing repositories and services that need to query the database, I can save myself a lot of writing by stuffing all the boring NHibernate push-ups in a DbTestFixture supertype – this includes building a configuration that connects to a test database, building a session factory, storing that session factory somewhere, re-creating the entire database schema in the test fixture setup, and running each test in a transaction that is automatically rolled back at the end of each test + a few convenience methods that allow me to flush the current session etc.
The DbTestFixture might look something like this (note that all my repositories take an instance of ISessionProvider in their ctor – that’s how they obtain the currently ongoing session, which is why I have a TestSessionProvider to inject into repositories under test):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
public class DbTestFixture { TestSessionProvider currentSessionProvider; ISession currentSession; ITransaction currentTransaction; bool commit; [TestFixtureSetUp] public void TestFixtureSetUp() { SessionFactoryHolder.Instance.CreateSchema(); using (currentSession = OpenSession()) { DoTestFixtureSetUp(); currentSession.Flush(); } } [SetUp] public void SetUp() { currentSession = OpenSession(); currentTransaction = currentSession.BeginTransaction(); currentSessionProvider = new TestSessionProvider(currentSession); DoSetUp(); currentSession.Flush(); } [TearDown] public void TearDown() { DoTearDown(); if (commit) { currentTransaction.Commit(); } else { currentTransaction.Rollback(); } currentTransaction.Dispose(); currentSession.Dispose(); } protected ISession OpenSession() { return SessionFactoryHolder.Instance.SessionFactory.OpenSession(); } protected ISessionProvider SessionProvider { get { return currentSessionProvider; } } protected virtual void DoTestFixtureSetUp() { } protected virtual void DoSetUp() { } protected virtual void DoTearDown() { } protected ISession CurrentSession { get { return currentSession; } } protected T Reload<T>(T t) where T : Base { SaveAndFlushAndClear(t); return CurrentSession.Get<T>(t.Id); } protected void SaveAndFlushAndClear(object obj) { CurrentSession.Save(obj); CurrentSession.Flush(); CurrentSession.Clear(); } protected void SaveAndFlush(object obj) { CurrentSession.Save(obj); CurrentSession.Flush(); } protected void Save(object obj) { CurrentSession.Save(obj); } protected void Flush() { CurrentSession.Flush(); } protected void DoNotRollBack() { commit = true; } protected class TestSessionProvider : ISessionProvider { readonly ISession currentSession; public TestSessionProvider(ISession currentSession) { this.currentSession = currentSession; } public ISession GetCurrentSession() { return currentSession; } public void Commit() { throw new System.NotImplementedException(); } public void RollBack() { throw new System.NotImplementedException(); } } } |
Then a fictional repository test might look as simple as this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
[TestFixture] public class TestMessageRepository : DbTestFixture { MessageRepository repo; Guid messageId; protected override void DoTestFixtureSetUp() { var message = new Message { CreatedAt = new DateTime(2003, 11, 11), Subject = "some subject", Body = "some body", }; CurrentSession.Save(message); messageId = message.Id; } protected override void DoSetUp() { repo = new MessageRepository(SessionProvider); } [Test] public void CanLoadSingleMessage() { var message = repo.Find(messageId); Assert.AreEqual(new DateTime(2003, 11, 11), message.CreatedAt); Assert.AreEqual("some subject", message.Subject); Assert.AreEqual("some body", message.Body); } } |
Note how DbTestFixture flushes in all the right places so I don’t need to worry about that.
This test fixture supertype can be used for all my database access tests, as well as integration testing. But what about unit tests? I am using Rhino Mocks, so my unit test fixture base looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class UnitTestFixture { protected readonly MockRepository mocks = new MockRepository(); protected T Stub<T>(params object[] paramsForConstructor) where T : class { return mocks.Stub<T>(paramsForConstructor); } protected T Mock<T>(params object[] paramsForConstructor) where T : class { return mocks.DynamicMock<T>(paramsForConstructor); } } |
Real simple – it just stores my MockRepository and gives me a few shortcuts to the mocks I care for. Then I inherit this further to ease testing e.g. my ASP.NET MVC controllers like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
public abstract class ControllerTestFixture<T> : UnitTestFixture where T : Controller { protected T controller; [SetUp] public void SetUp() { controller = CreateController(); } protected abstract T CreateController(); } |
As you can see, I make it a real “fixture” – the controllers I am about to test will fit into this fixture like a glove, and I will certainly never forget to instantiate my controller only once, because I start out by implemeting that part in the implementation of the CreateController method.
A controller test might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[TestFixture] public class TestAuthenticationController : ControllerTestFixture<AuthenticationController> { ISessionContext sessionContext; IAuthenticationService authenticationService; protected override AuthenticationController CreateController() { sessionContext = mocks.DynamicMock<ISessionContext>(); authenticationService = mocks.DynamicMock<IAuthenticationService>(); return new AuthenticationController(sessionContext, authenticationService); } // tests down here... } |
One thought on “Respect your test code #2: Create base classes for your test fixtures”