Friday, March 21, 2008

Thou Shalt Not Mock Thyself

We had some Unit Tests in our application recently that were failing. We are using TypeMock.Net (now TypeMock Isolator) to mock out some of the objects in the tests. In tracking down the issue,  I learned that you should not mock yourself. To better explain this, please consider the following trivial class.

UPDATE: Please read the comment (and see the code) from Eli Lopian (founder of TypeMock) on how to mock the class without mocking the constructor. Thanks Eli!

using System.Configuration;

 

namespace SelfMocking

{

    public class MyClass

    {

        public readonly string Text1;

        public readonly bool IsValid;

 

        public string SettingValue1()

        {

            return System.Configuration.ConfigurationManager.AppSettings["Value1"];

        }

 

        public MyClass(string text1)

        {

            Text1 = text1;

            IsValid = !string.IsNullOrEmpty(Text1);

        }

    }

}

 

Then we will write the following test fixture and test to mock the call to the SettingValue1 property and return a mocked string value. To do that I am going to mock the MyClass object and return the mocked value for all calls to MyClass.SettingValue1. See the code below:

using System.Collections.Specialized;

using NUnit.Framework;

using NUnit.Framework.SyntaxHelpers;

using TypeMock;

 

namespace SelfMocking

{

    [TestFixture]

    public class MyClassTest

    {

        [TestFixtureSetUp]

        public void FixtureSetup()

        {

            MockManager.Init();

        }

 

        [TestFixtureTearDown]

        public void FixtureTearDown()

        {

            MockManager.Verify();

        }

 

        [Test]

        public void Test_MyClass()

        {

            MockMyClass();

            MyClass testMyClass = new MyClass("test1");

            Assert.That(testMyClass.SettingValue1(), Is.EqualTo("MockedValue1"));

            Assert.That(testMyClass.Text1, Is.Not.Null);

            Assert.That(testMyClass.IsValid, Is.True);

        }

 

        private void MockMyClass()

        {

            Mock mockMyClass = MockManager.Mock(typeof (MyClass));

            mockMyClass.AlwaysReturn("SettingValue1", "MockedValue1");

        }

    }

}

When I attempt to execute the unit test Test_MyClass, it returns an fails with the following message:

NUnit.Framework.AssertionException:   Expected: not null  But was:  null

So it is correctly mocking the SettingsValue1() call, but the property Text1 is not being set. If I debug the test and step through the code as it is being executed, I see that the constructor for MyClass is never being executed, even though my test is calling it. The reason it is not being executed, is that the first thing I do is create a mock of MyClass, but I do not instruct the mock object to mock the constructor, so when I call the constructor, it is just ignored. Therefore, the Text1 property is never set and is in fact still Null.

In order to properly mock this, we need to go a level deeper and actually mock the AppSettings call on the System.Configuration.ConfigurationManager class. Below is the revised code:

using System.Collections.Specialized;

using NUnit.Framework;

using NUnit.Framework.SyntaxHelpers;

using TypeMock;

 

namespace SelfMocking

{

    [TestFixture]

    public class MyClassTest

    {

        private NameValueCollection nameValueCollection = new NameValueCollection();

 

        [TestFixtureSetUp]

        public void FixtureSetup()

        {

            MockManager.Init();

            nameValueCollection["Value1"] = "MockedValue1";

        }

 

        [TestFixtureTearDown]

        public void FixtureTearDown()

        {

            MockManager.Verify();

        }

 

        [Test]

        public void Test_MyClass()

        {

            MockAppSettings();

            MyClass testMyClass = new MyClass("test1");

            Assert.That(testMyClass.SettingValue1(), Is.EqualTo("MockedValue1"));

            Assert.That(testMyClass.Text1, Is.Not.Null);

            Assert.That(testMyClass.IsValid, Is.True);

        }

 

        private void MockAppSettings()

        {

            Mock mockConfigurationManager = MockManager.Mock(typeof (System.Configuration.ConfigurationManager));

            mockConfigurationManager.ExpectGetAlways("AppSettings", nameValueCollection);

        }

    }

}

As you can see in this new version, I am mocking ConfigurationManager and have created a new NameValueCollection into which I have set the key Value1 = "MockedValue1" and then when the class calls AppSettings, the MockManager will return my NameValueCollection instead.

So, keep in mind that if you are mocking objects, it is never a good idea to mock the class that you are actually trying to test, as it could have unexpected results.

2 comments:

Eli Lopian said...

Paige,
Actually your first test is quite good. The only piece missing is to tell Typemock to call the Original Constructor by using Constructor.NotMocked, see Mocking Constructors

Some more tips:
* You should verify the mocks after each test, in the [TearDown]
* MockManager.Init is done automatically
* You can use the Tracer to 'debug' your mocks

So here is the test:

[TestFixture]
public class MyClassTest
{
[TearDown]
public void TearDown()
{
MockManager.Verify();
}

[Test]
public void Test_MyClass()
{
MockMyClass();
MyClass testMyClass = new MyClass("test1");
Assert.That(testMyClass.SettingValue1(), Is.EqualTo("MockedValue1"));
Assert.That(testMyClass.Text1, Is.Not.Null);
Assert.That(testMyClass.IsValid, Is.True);
}

private void MockMyClass()
{
Mock mockMyClass = MockManager.Mock<MyClass>(Constructor.NotMocked);
mockMyClass.AlwaysReturn("SettingValue1", "MockedValue1");

}
}

Paige Cook said...

Eli,

Thanks so much... I had not noticed the Constructor.NotMocked option before. I will make sure to read up on Mocking Constructors in the documentation. I appreciate you pointing that out!

- Paige