Unit testing with test harnesses

One thing I have noticed from many developers when setting up unit tests is that they like their Setup() methods. This is fine for example (shameless plug) if you are looking to integration test against something like Glass Mapper for Sitecore and want to initialise the configuration before you run your test. However, all too often, I see it used to set up things like mocks, and its this that I disagree with. So I guess for want of a better term – I created what I will (I think it’s probably technically a Builder pattern really) a ‘Test Harness’ pattern.

The Problem

Take a read through the listing below.


using Moq;
using NUnit.Framework;
using Cardinal.Adapters;

namespace Cardinal.Tests
{
    [TestFixture]
    public class TestClass
    {
        private Mock<ISitecorePage> sitecorePageMock;
        private DependentClass dependentClass;

        [TestFixtureSetUp]
        public void Setup()
        {
            sitecorePageMock = new Mock<ISitecorePage>();
            dependentClass = new DependentClass(sitecorePageMock.Object);
        }

        [Test]
        public void Can_get_some_result_successfully_when_in_page_editor_mode()
        {
            // Assign
            sitecorePageMock.Setup(x => x.IsPageEditor).Returns(true);
            sitecorePageMock.Setup(x => x.IsPreview).Returns(false);
            sitecorePageMock.Setup(x => x.IsNormal).Returns(false);

            // Act
            var result = dependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }

        [Test]
        public void Can_get_a_result_when_preview_mode()
        {
            // Assign - in order to be safe I am now forced to ensure all values are reset
            sitecorePageMock.Setup(x => x.IsPageEditor).Returns(false);
            sitecorePageMock.Setup(x => x.IsPreview).Returns(true);
            sitecorePageMock.Setup(x => x.IsNormal).Returns(false);

            // Act
            var result = dependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }

        [Test]
        public void Shakey_can_get_a_result_when_normal_mode()
        {
            // Assign - surely this is more sensible, but we are now relying on what have executed before
            HomePageItem item = new HomePageItem();
            sitecorePageMock.Setup(x => x.HomePageItem).Returns(item);

            // Act - This could be affected by page being in edit / preview
            var result = dependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }
    }
}

In the code sample above, the setup takes care of the initialisation of the mocked object. As you read down, you will start to see that my mocked behaviour has to be quite explicit in order to ensure all of the values are correct. In the case of the final method Shakey_can_get_a_result_when_normal_mode(). Running this test in isolation will be fine, HOWEVER – I could in theory get any of the mocked behaviour from any of my other tests when ran as part of the suite thus polluting my tests and potentially giving false positives or negatives. This in my opinion is badly flawed – but then, how can we cope with objects with many dependencies.

The Test Harness

Now consider the same tests written differently using a test harness

using Moq;
using NUnit.Framework;
using Cardinal.Adapters;

namespace Cardinal.Tests
{
    [TestFixture]
    public class TestClass
    {
        [Test]
        public void Can_get_some_result_successfully_when_in_page_editor_mode()
        {
            // Assign
            var testHarness = new DependantClassTestHarness();
            testHarness.SetPageEditorMode();

            // Act
            var result = testHarness.DependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }

        [Test]
        public void Can_get_a_result_when_preview_mode()
        {
            // Assign
            var testHarness = new DependantClassTestHarness();
            testHarness.SetPreviewMode();

            // Act
            var result = testHarness.DependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }

        [Test]
        public void Shakey_can_get_a_result_when_normal_mode()
        {
            // Assign
            var testHarness = new DependantClassTestHarness();
            HomePageItem item = new HomePageItem();
            testHarness.SitecorePageMock.Setup(x => x.HomePageItem).Returns(item);

            // Act - this is no longer dependant on anything other than its own mock setup
            var result = testHarness.DependentClass.DoSomething();


            // Assert
            Assert.AreEqual("Expected Correct Result", result);
        }

        public class DependantClassTestHarness
        {
            public DependantClassTestHarness()
            {
                SitecorePageMock = new Mock<ISitecorePage>();
                DependantClass = new DependantClass(SitecorePageMock.Object);
            }

            public Mock<ISitecorePage> SitecorePageMock { get; private set; }

            public DependantClass DependantClass { get; private set; }

            public void SetPageEditorMode()
            {
                // Here we define the common behaviour of being in edit mode
                SitecorePageMock.Setup(x => x.IsPageEditor).Returns(true);
                SitecorePageMock.Setup(x => x.IsPreview).Returns(false);
                SitecorePageMock.Setup(x => x.IsNormal).Returns(false);
            }

            public void SetPreviewMode()
            {
                // Here we define the common behaviour of being in preview mode
                SitecorePageMock.Setup(x => x.IsPageEditor).Returns(false);
                SitecorePageMock.Setup(x => x.IsPreview).Returns(true);
                SitecorePageMock.Setup(x => x.IsNormal).Returns(false);
            }
        }
    }
}

In the above, not only is the DependantClassTestHarness a nice easy way to start with a fresh copy of your mocked dependencies & class to test, meaning things like lazy loading will definitely NOT have taken effect and that your mock will not be subject previous setup’s. I have also defined grouped standard behaviours using methods to allow common scenarios to be set up.

This also has the added bonus, that if I have a class to test with say – 6 or 7 dependencies, I don’t have to go through the pain of setting each in every individual test.

Advertisements

2 thoughts on “Unit testing with test harnesses

    • Thanks dru 😀

      I have used [SetUp] in the past, both work :D. I like with this approach the fact that I can pick and choose common setups more easily this way in a way that I can easily share test harnesses between classes if I choose to.

      Also with setup – generally the variables would be global which means they HAVE to be explicitly cleared in some way in your setup method.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s