chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
I'm currently using TypeMock v7.0.6.0, Visual Studio 2010 v10.0.40219.1 SP1Rel

When I use a property to store a mocked object, and the subsequently call Isolate.WhenCalled(), a new object is stored in the property, and all instance variables on the new object are set to null. The basic test below demonstrates the issue, and shows that it works fine when call with a local variable or field.

I noticed the issue in a test of ours where we mocked one object (called "A"), assigned it as a dependency to another, non-mocked object (called "B"), and then called Isolate.WhenCalled() on the mocked object(creates new one called "C"). The original mocked object stored(A) as a dependency(in B) and the "new" mocked object(C) created afterwards were not the same object, so the Isolate.WhenCalled wasn't taking affect in the test (B.A != C, so B.A doesn't have mocking on the method), and our tests failed.

using MbUnit.Framework;
using TypeMock.ArrangeActAssert;

namespace TypeMockPropertyTest
{
    public class TestClass
    {
        private readonly object _dummyObject;
        public string TestString;

        public TestClass()
        {
            _dummyObject = new object();
            TestString = "This is a ";
        }

        public object Test()
        {
            return TestString + GetDummyString();
        }

        public object GetDummyString()
        {
            return _dummyObject.ToString();
        }
    }

    [TestFixture]
    public class Tester
    {
        private TestClass fakeField;
        private TestClass fakeProperty { get; set; }

        [Test]
        [Isolated]
        public void VariableTest()
        {
            var fake = Isolate.Fake.Instance<TestClass>(Members.CallOriginal);
            Isolate.WhenCalled(() => fake.GetDummyString()).WillReturn("Test");

            // passes
            Assert.AreEqual("This is a Test", fake.Test());
        }

        [Test]
        [Isolated]
        public void FieldTest()
        {
            fakeField = Isolate.Fake.Instance<TestClass>(Members.CallOriginal);
            Isolate.WhenCalled(() => fakeField.GetDummyString()).WillReturn("Test");

            // passes
            Assert.AreEqual("This is a Test", fakeField.Test());
        }

        [Test]
        [Isolated]
        public void PropertyTest()
        {
            // This first call operates the same as in the other tests
            fakeProperty = Isolate.Fake.Instance<TestClass>(Members.CallOriginal);
            // But this call (and each successive call) will create a new object for fakeProperty, and all instance variables are null or default.
            Isolate.WhenCalled(() => fakeProperty.GetDummyString()).WillReturn("Test");

            // Fails
            Assert.AreEqual("This is a Test", fakeProperty.Test());
        }
    }
}
asked by peterf (1.2k points)

2 Answers

0 votes
Hi peterf,

There are two things resulting this behavior, the first one is the fact the test is placing an expectation on a test class property and the second one is the default faking behavior when a chain of methods (e.g. a().b().c()) is faked in a single when called.

Expectation on test class property
The main difference between the two examples, first with field and second with property, is that a property getter is actually a method call by itself. When the getter appears inside the WhenCalled, it is actually referring to the method (e.g. get_fakeProperty()) and not the value stored in it before.

For example:
[TestFixture]
public class TestWithProperty
{
    private TestClass fakeProperty { get; set; }

    [Test]
    [Isolated]
    public void VariableTest()
    {
        fakeProperty = null;

        Isolate.WhenCalled(() => fakeProperty.GetDummyString()).WillReturn("Test");
        // Same as:
        //Isolate.WhenCalled(() => this.get_fakeProperty().GetDummyString()).WillReturn("Test");

        Assert.AreEqual("Test", fakeProperty.GetDummyString());
    }
}


What we see in this example is that the actual faked behavior is defined on the getter method and not on the value which was sent to the setter method before that (in our example, a null value). This feature allows tests to define faked behavior on real instances. This is known as live object. In the original example, the actual instance which was faked (just a single method) was the test class itself, on which the property is defined (this.get_fakeProperty()).

Default faking behavior when a chain of methods (e.g. a().b().c()) is faked
When faking multiple methods in a single WhenCalled (aka chain) all the instances along the path are faked. The default fake behavior depends on the originating fake. For example, if we have fakeA which is a recursive fake, a method called on it, like GetB() will also return a recursive fake. But, if the originating instance is a fake instantiated with CallOriginal or instead a live object, then the fakes defined along the path are also CallOriginal fakes. The difference in this case, is that when the fake is created, it's constructor will be ignored. This is similar to creating a fake with Isolate.Fake.Instance<TestClass>(Members.CallOriginal, ConstructorWillBe.Ignored).
In the original example, what we got was an instance that called the actual method (Test) but since the constructor was not executed, the value of the TestString field was not initialized.

Solution
Assuming the intention was to set a behavior on the fake instance it's possible to do one of the next:
1. Avoid a usage of property in the test class, instead use fields.
2. Before calling WhenCalled, extract the property value into a local variable and use the variable inside the [i]WhenCalled[i/]

For example:
fakeProperty = Isolate.Fake.Instance<TestClass>(Members.CallOriginal);

var fake = fakeProperty;
Isolate.WhenCalled(() => fake.GetDummyString()).WillReturn("Test");


Please let us know if it's clear.
answered by alex (17k points)
0 votes
That's perfectly clear. Thank you for the quick and complete response, it really helps.
answered by peterf (1.2k points)
...