Want to show your appreciation?
Please a cup of tea.

Saturday, June 27, 2009

Rhino.Mocks Ordered Expectations for AAA Syntax

There was a question posted to the Google Group about how to make ordered expectations using AAA syntax in Rhino.Mocks. As people being increasingly aware of over specification using the record/reply model. Arrange-Action-Assert model is getting adopted by more and more developers. In the question, Alex was asking if is a way to make the ordered expectations using AAA syntax instead of the workaround he used below which is, IMHO, quite tedious to write and understand.

        public void ViewTitleSetBeforeShownInWorkspace()
        {
            //arrange
            var mockView = MockRepository.GenerateMock<IView>();
            bool viewTitleSet = false;
            mockView.Stub(x => x.Title = Arg<string>.Is.Anything)
                .WhenCalled(a => { viewTitleSet = true; });
            mockView.Stub(x => x.ShowInWorkspace("MyWorkspace"))
                .WhenCalled(a => { Assert.IsTrue(viewTitleSet, 
                    "View title should have been set first"); });

            //act
            var target = new Presenter(mockView);
            target.Initialize();

            //assert
            mockView.AssertWasCalled(x => x.ShowInWorkspace("MyWorkspace"));
        }

I agree with Ayende Rahien that ordered expectation is rare. But it does happen and I wish there is a way to do it when I need it. I again agreed that duplicating full ordering support from record/reply into AAA is too complex than worth. It would be not only over killed but also easily abused.

But, maybe there is a simpler way to the goal. All I need is to ensure one method should have been called before another, I want a way to verify this as easy as AssertWasCalled.

How about something like below:

            mockBefore.AssertWasCalled(b => b.MethodBefore())
                .Before(mockAfter.AssertWasCalled(a => a.MethodAfter()));

I loves to use open source tools because if nobody doing it then I do it myself. I checked out the Rhino.Mocks source code from Subversion repository. It turned out to be quite straightforward to make it work. Thanks to Ayende for the nice design. Going around the code and making changes were very easy. I was a bit afraid of scratching the crystal when making those changes. Ayende, please forgive me if I did.

Rhino.Mocks does record each call so that it can later use the information to verify the expectations. But the current design only records call parameters. I changed it to record a newly introduced CallRecord object.

    public class CallRecord
    {
        private static long sequencer = long.MinValue;

        internal CallRecord()
        {
            Sequence = Interlocked.Increment(ref sequencer);
        }
        internal object[] Arguments { get; set; }
        internal long Sequence { get; private set; }
        internal MethodInfo Method { get; set; }
    }

The key in this object is the sequencer. Every time a new CallRecord is created, the sequencer was increased in a thread safe manner. And the current sequence is recorded with the call. So each CallRecord object in the system has a unique Sequence value that can be used to determine the time order of the calls at later time.

The Arguments property is used by the original Rhino.Mocks to check expectations. Having Method property here was due to my laziness. There should be a better way to handle this for a lighter weight of CallRecord.

Next is to make the AssertWasCalled methods to return the CallRecord. Since the AssertWasCalled can match multiple method invocations. I made them return an IList<CallRecord>.

With the CallRecord information on hand, an extension method below can easily compare the order of the calls:

        public static void Before(this IList<CallRecord> beforeCalls, IList<CallRecord> afterCalls)
        {
            long maxBefore = long.MinValue;
            CallRecord latestBeforeCall = null;
            foreach (var call in beforeCalls)
            {
                var sequence = call.Sequence;
                if (sequence > maxBefore)
                {
                    maxBefore = sequence;
                    latestBeforeCall = call;
                }
            }

            long minAfter = long.MaxValue;
            CallRecord earliestAfterCall = null;
            foreach (var call in afterCalls)
            {
                var sequence = call.Sequence;
                if (sequence < minAfter)
                {
                    minAfter = sequence;
                    earliestAfterCall = call;
                }
            }
            if (maxBefore>minAfter)
            {
                throw new ExpectationViolationException(
                    "Expected that calls to " + latestBeforeCall.Method + 
                    " occurs before " + earliestAfterCall.Method + 
                    ", but the expectation is not satisfied.");

            }
        }

Again, I was lazy, the exception message should have been better formed, for example including the more detailed method signature and the real parameters. (Update: the API has been enhanced)

With all those in place, the final bit is to create a test cases to verify it works.

    [TestFixture] public class BeforeExtensionMethodTest
    {
        public interface IBefore  { void MethodBefore(); }

        public interface IAfter { void MethodAfter(); }

        [Test] public void Before_succeeds_if_beforeCalls_occured_before_afterCalls()
        {
            var mockBefore = MockRepository.GenerateStub<IBefore>();
            var mockAfter = MockRepository.GenerateStub<IAfter>();
            mockBefore.MethodBefore();
            mockBefore.MethodBefore();
            mockAfter.MethodAfter();
            mockAfter.MethodAfter();
            mockAfter.MethodAfter();
            mockBefore.AssertWasCalled(b => b.MethodBefore())
                .Before(mockAfter.AssertWasCalled(a => a.MethodAfter()));
        }

        [ExpectedException(typeof(ExpectationViolationException))]
        [Test] public void Before_chokes_if_one_of_beforeCalls_occured_after_any_of_afterCalls()
        {
            var mockBefore = MockRepository.GenerateStub<IBefore>();
            var mockAfter = MockRepository.GenerateStub<IAfter>();
            mockBefore.MethodBefore();
            mockAfter.MethodAfter();
            mockBefore.MethodBefore();
            mockAfter.MethodAfter();
            mockAfter.MethodAfter();
            mockBefore.AssertWasCalled(b => b.MethodBefore())
                .Before(mockAfter.AssertWasCalled(a => a.MethodAfter()));
        }
    }

The full patch for r2212 of https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/mocks/ can be downloaded here. Alex's original problem in the beginning now can be rewritten neatly.

        public void ViewTitleSetBeforeShownInWorkspace()
        {
            //arrange
            var mockView = MockRepository.GenerateMock<IView>();

            //act
            var target = new Presenter(mockView);
            target.Initialize();

            //assert
            mockView.AssertWasCalled(x => { x.Title = Arg<string>.Is.Anything; })
                .Before(mockView.AssertWasCalled(x => x.ShowInWorkspace("MyWorkspace")));
        }

Update 7/18: I made my customized version of Rhino Mocks available if you want to give it a try. It contains other enhancements.

2 comments:

Anonymous said...

This seems like a pretty interesting idea. But I imagine it would be too combersome to do anything other than simple order validation, because once you start comparing the order of multiple objects and methods to each other, the syntax would be more confusing than what we already do.

It kind of sounds like an old SAT puzzle: Method A is older than Method B, but younger than Method C. If A is called....

Kenneth Xu said...

Michael, you are right but this was further enhanced. How about below for your puzzle?

(m.ActivityOf(m=>m.MethodB) < m.ActivityOf(m=>m.MethodA) < m.ActivityOf(m=>m.MethodC)).AssertOccured

Post a Comment