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
Welcome to Typemock Community! Here you can ask and receive answers from other community members. If you liked or disliked an answer or thread: react with an up- or downvote.
0 votes
I see that by default the expectations are not ordered, however I have come across a situation where the expectations will not be satisfied unless I order them specifically as there are called in the class being tested.

We are using NUnit v2.4.1 and Isolator v5.1.2.

Here is some short code that demonstrates the problem:

using System;
using NUnit.Framework;
using TypeMock;

namespace TypemockTesting
{
    public class ClassUnderTest
    {
        public void TestMethod()
        {
            DateTime dateTime = SingletonClass.Instance.GetDateTime();
            Console.WriteLine(dateTime.ToString("dd-MMM-yyy  HH:mm:ss"));

            SingletonClass.Instance.SetDateTime(new DateTime(1969, 1, 18));

            dateTime = SingletonClass.Instance.GetDateTime();
            Console.WriteLine(dateTime.ToString("dd-MMM-yyy  HH:mm:ss"));
        }
    }

    public class SingletonClass
    {
        private static SingletonClass _instance;
        public static SingletonClass Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new SingletonClass();
                }
                return _instance;
            }
        }

        public DateTime GetDateTime()
        {
            throw new InvalidOperationException("GetDateTime should not get called, it should hit the mocked instance!");
        }

        public void SetDateTime(DateTime dateTime)
        {
            throw new InvalidOperationException("SetDateTime should not get called, it should hit the mocked instance!");
        }
    }

    [TestFixture]
    public class ClassUnderTestUnitTests
    {
        [Test, VerifyMocks]
        public void TestMethod_WhenCalled_TestWorks()
        {
            using(RecordExpectations recorder = RecorderManager.StartRecording())
            {
                // order of expectations = GetDateTime, SetDateTime, GetDateTIme. this works

                SingletonClass.Instance.GetDateTime();
                recorder.Return(new DateTime(1969, 1, 17));

                SingletonClass.Instance.SetDateTime(DateTime.MinValue);

                SingletonClass.Instance.GetDateTime();
                recorder.Return(new DateTime(1969, 1, 17));
            }

            ClassUnderTest testClass = new ClassUnderTest();
            testClass.TestMethod();
        }

        [Test, VerifyMocks]
        public void TestMethod_WhenCalled_TestDoesntWorkWithRepeatAlways()
        {
            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                // using RepeatAlways on GetDateTime

                SingletonClass.Instance.GetDateTime();
                recorder.Return(new DateTime(1969, 1, 17)).RepeatAlways();

                SingletonClass.Instance.SetDateTime(DateTime.MinValue);
            }

            ClassUnderTest testClass = new ClassUnderTest();
            testClass.TestMethod();
        }

        [Test, VerifyMocks]
        public void TestMethod_WhenCalled_TestDoesntWorkWithRepeatedExpectation()
        {
            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                // repeat the GetDateTime expectation explicitly

                SingletonClass.Instance.GetDateTime();
                recorder.Return(new DateTime(1969, 1, 17));

                SingletonClass.Instance.GetDateTime();
                recorder.Return(new DateTime(1969, 1, 17));

                SingletonClass.Instance.SetDateTime(DateTime.MinValue);
            }

            ClassUnderTest testClass = new ClassUnderTest();
            testClass.TestMethod();
        }
    }
}


It seems as long as the order of method calls matches the order of the expectations, the test will succeed.


William Bain.
asked by (600 points)

1 Answer

0 votes
I'm not sure whether I'd call it a bug or not, and here's why:

When you call "Singleton.Instance," it's not really retrieving a static item, it's instantiating something and returning that. Since it's never used anywhere else in the code except when it's being mocked, the real static field "_instance" will always be null and the "get" will always instantiate a new one. If you look in the Tracer utility when you run the tests, that's actually exactly what you see - the expectations are set on different instances of the SingletonClass object. For example, in a test that sets an expectation on SingletonClass.Instance.GetDateTime twice, you'll see that there are expectations set up around two different instances of the SingletonClass.

The reason it works when you order the statements, then, is because the mock setup is saying, "For the first instance retrieved, do this; for the second instance, do this; for the third instance, do that." If you change the order of the statements in the mock setup, the expectations on the instances are incorrectly set up - you'd be saying "on this instance, expect a call to GetDateTime" when in the executing code that instance is the one that gets "SetDateTime" called on it. The RepeatAlways doesn't work because it's setting the expectation on the instance of SingletonClass that gets first retrieved; in the subsequent calls to SingletonClass.Instance, you're working with different instances that the expectations aren't set on.

That said, I can see where logically you'd think this should work, and I agree, it appears that it should work. That's why I'm not sure if I'd call it a bug or not. Internally I can see why it doesn't work, but from the outside I can see why I might want it to.

Regardless, you can still make your tests work by setting up your expectations around a specific instance of the SingletonClass. I've updated the two tests that were failing so they pass the way you think they should. (I also updated the way the tests assert success/failure so it's not a manual check against the console output.)

[TestFixture]
public class ClassUnderTestUnitTests 
{
  TextWriter _testOut = null;
  TextWriter _originalOut = null;

  [SetUp]
  public void SetUp()
  {
    this._originalOut = Console.Out;
    this._testOut = new StringWriter();
    Console.SetOut(this._testOut);
  }

  [TearDown]
  public void TearDown()
  {
    Console.SetOut(this._originalOut);
    this._originalOut = null;
    this._testOut.Dispose();
    this._testOut = null;
  }

  private string BuildAssertionDateString(DateTime date1, DateTime date2)
  {
    return String.Format("{0:dd-MMM-yyy  HH:mm:ss}{2}{1:dd-MMM-yyy  HH:mm:ss}{2}", date1, date2, Environment.NewLine);
  }

  [Test, VerifyMocks] 
  public void TestMethod_WhenCalled_WorksWithRepeatAlways() 
  {
    SingletonClass concreteInstance = SingletonClass.Instance;
    using (RecordExpectations recorder = RecorderManager.StartRecording()) 
    { 
      SingletonClass mock = SingletonClass.Instance;
      recorder.Return(concreteInstance).RepeatAlways();

      concreteInstance.GetDateTime(); 
      recorder.Return(new DateTime(1969, 1, 17)).RepeatAlways(); 

      concreteInstance.SetDateTime(DateTime.MinValue); 
    } 

    ClassUnderTest testClass = new ClassUnderTest(); 
    testClass.TestMethod(); 
    Assert.AreEqual(this.BuildAssertionDateString(new DateTime(1969, 1, 17), new DateTime(1969, 1, 17)), this._testOut.ToString());
  }

  [Test, VerifyMocks] 
  public void TestMethod_WhenCalled_WorksWithRepeatedExpectation() 
  { 
    SingletonClass concreteInstance = SingletonClass.Instance;
    using (RecordExpectations recorder = RecorderManager.StartRecording()) 
    { 
      SingletonClass mock = SingletonClass.Instance;
      recorder.Return(concreteInstance).RepeatAlways();

      concreteInstance.GetDateTime(); 
      recorder.Return(new DateTime(1969, 1, 17)); 

      concreteInstance.GetDateTime(); 
      recorder.Return(new DateTime(1969, 1, 17)); 

      concreteInstance.SetDateTime(DateTime.MinValue); 
    } 

    ClassUnderTest testClass = new ClassUnderTest(); 
    testClass.TestMethod(); 
    Assert.AreEqual(this.BuildAssertionDateString(new DateTime(1969, 1, 17), new DateTime(1969, 1, 17)), this._testOut.ToString());
  } 
} 


The major change there is that I'm getting the instance outside the mock setup, then I'm setting up my expectations against that specific instance.

You could probably also fix this by changing the implementation of SingletonClass to instantiate the _instance field in the static constructor rather than via lazy evaluation. Then in your mocks just let the static constructor run on the SingletonClass and you'll always be setting mocks up against the same instance.
answered by (6.7k points)
...