I was again (last one was about ordered expectations for AAA) inspired by a question posted on the Rhino Mocks support email group, expect a call to any one of overloaded method. How do we do that? I have proposed a solution and that was pretty much what I did before. When we talk about TDD, we write the test before implementation. And sometimes, designer writes the test cases for somebody else to complete the implementation.
In many cases, all we care is that one of the send methods of the notification service is called. Which exact send method to call is all up to the implementation.
The Syntax
There got to be a better why to do this! The same may not necessary only apply to overloaded methods. For any methods, I should be able to just write something like this:
ma.AssertWasCalled(a=>a.Foo()) or mb.AssertWasCalled(b=>b.Bar());
The experience gained from my work on the ordered expectation tells me that this is very possible. All I need to figure out is the syntax for this. May be
ma.AssertWasCalled(a=>a.Foo()).Or(mb.AssertWasCalled(b=>b.Bar()));
or even better to use operator overload:
ma.AssertWasCalled(a=>a.Foo()) | mb.AssertWasCalled(b=>b.Bar());
Well, the problem is that AssertWasCalled fails right away when a.Foo() is not called. Why do we have to fail it? Why cannot we leave it to the test framework to do the assert? But anyway, in order to not break the existing contract of AssertWasCalled, we'll have to use something different. How about this?
Assert.IsTrue(ma.ActivityOf(a=>a.Foo()) || mb.ActivityOf(b=>b.Bar()));
Have more methods, not problem!
Assert.IsTrue(foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything)) || foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything, Arg<Another>.Is.NotNull)) || foo.ActivityOf(a=>a.Foo(Arg<Something>.Is.Anything, Arg<Another>.Is.NotNull, Arg<int>Is.Anything)));
This syntax give us a lot of flexibility. I can use operator overload for ordered expectation as well.
Assert.IsTrue(ma.ActivityOf(a=>a.Foo()) < mb.ActivityOf(b=>b.Bar()));
And there are unlimited possibilities, some examples below:
// chained ordering
(m.ActivityOf(...) > m.ActivityOf(...) > m.ActivityOf(...)).AssertOccured;
// mixed or ordering
(m.ActivityOf(...) | m.ActivityOf(...)) < m.ActivityOf(...)).AssertOccured
var sent = (m.ActivityOf(a=>a.Sent(x)) | m.ActivityOf(a=>a.Sent(x, y))); Assert.IsTrue(sent); // assert sent is called Assert.IsTrue(sent < m.ActivityOf(a=>a.Close())); // assert sent called before close
Implementation
This is exciting, isn't it? How difficult to implement this? I give it a try by building on top of the changes that I made for the ordered expectation in the previous post. It turn out to be quite easy. Although the code below is no where close to perfect, and indeed it is quick and dirty to some extend, but it served well as proof of concept to introduce this technique.
Here we go the code. The Activities class is the core. It provides implementation for all the operator overload.
public class Activities : IComparable<Activities> { private static readonly CallRecord[] _emptyRecords = new CallRecord[0]; private readonly IList<CallRecord> _callRecords = _emptyRecords; private readonly ExpectationViolationException _exception; public Activities(IList<CallRecord> callRecords) { if (callRecords == null || callRecords.Count == 0) throw new ArgumentException( "Must not be null or empty.", "callRecords"); _callRecords = callRecords; } public Activities(ExpectationViolationException exception) { if(exception==null) throw new ArgumentNullException("exception"); _exception = exception; } public bool Occured { get { return _callRecords.Count > 0; } } public Activities OccuredBefore(Activities other) { if (!Occured) return this; if (!other.Occured) return other; var thisLast = GetLast(); CallRecord otherFirst = other._callRecords[0]; return thisLast.Sequence < otherFirst.Sequence ? other : new Activities(NewOrderException(thisLast, otherFirst)); } private ExpectationViolationException NewOrderException( CallRecord before, CallRecord after) { return new ExpectationViolationException( "Expected that call " + before.Method + " occurs before call " + after.Method + ", but the expectation is not satisfied."); } public Activities OccuredAfter(Activities other) { if (!Occured) return this; if (!other.Occured) return other; CallRecord otherLast = other.GetLast(); CallRecord thisFirst = _callRecords[0]; return otherLast.Sequence < thisFirst.Sequence ? other : new Activities(NewOrderException(otherLast, thisFirst)); } public Activities Or(Activities other) { if (Occured) return this; if (other.Occured) return other; return new Activities(new ExpectationViolationException( this._exception.Message + "\nor\n" + other._exception.Message)); } public Activities First { get { return Occured ? new Activities(new CallRecord[] {_callRecords[0]}) : this; } } public Activities Last { get { return Occured ? new Activities(new CallRecord[] { GetLast() }) : this; } } private CallRecord GetLast() { return _callRecords[_callRecords.Count - 1]; } public int CompareTo(Activities other) { if (ReferenceEquals(this, other)) return 0; return OccuredBefore(other) ? -1 : 1; } public static implicit operator bool(Activities activities) { return activities.Occured; } public static Activities operator <(Activities a1, Activities a2) { return a1.OccuredBefore(a2); } public static Activities operator >(Activities a1, Activities a2) { return a1.OccuredAfter(a2); } public static Activities operator |(Activities a1, Activities a2) { return a1.Or(a2); } public static Activities operator ^(Activities a1, Activities a2) { return OneOf(a1, a2); } public static bool operator true(Activities a) { return a.Occured; } public static bool operator false(Activities a) { return !a.Occured; } public void AssertOccured() { if (_exception != null) throw _exception; } internal static Activities ExactOneOf(params Activities[] activitiesList) { Activities one = null; foreach (var activities in activitiesList) { if (!activities.Occured) continue; if (one == null) one = activities; else return new Activities( new ExpectationViolationException( "Both " + one._callRecords[0].Method + " and " + activities._callRecords[0].Method + " was called")); } if (one != null) return one; StringBuilder sb = new StringBuilder("None of below is satisfied:"); foreach (var activities in activitiesList) { sb.Append('\n').Append(activities._exception.Message); } return new Activities(new ExpectationViolationException(sb.ToString())); } }
The other class holds a few extension methods that glues this new API to existing Rhino Mocks.
public static class Mockery { public static Activities ActivityOf<T>(this T mock, Action<T> action, Action<IMethodOptions<object>> setupConstraints) { try { return new Activities(mock.AssertWasCalled(action, setupConstraints)); } catch (ExpectationViolationException e) { return new Activities(e); } } public static Activities ActivityOf<T>(this T mock, Action<T> action) { return ActivityOf(mock, action, DefaultConstraintSetup); } public static Activities ActivityOf<T>(this T mock, Function<T, object> func, Action<IMethodOptions<object>> setupConstraints) { return ActivityOf(mock, new Action<T>(t => func(t)), setupConstraints); } public static Activities ActivityOf<T>(this T mock, Function<T, object> func) { return ActivityOf(mock, func, DefaultConstraintSetup); } public static void Assert(Activities activities) { activities.AssertOccured(); } public static Activities ExactOneOf(params Activities[] activitiesList) { return Activities.ExactOneOf(activitiesList); } private static void DefaultConstraintSetup(IMethodOptions<object> options) { } }
You can see that the implementation of the first ActivityOf method is very quick and dirty. The idea was to keep this POC code simple enough to illustrate the technique.
The Failure Message
There is one minor issue with using test framework's Assert.IsTrue. When the assert fails, it simply tells you that you expected true but got false. I don't see this as a big issue because with today's tool you can easily click on the error to jump to the exact line of code. In most of time, the code gives much more information then the message itself. But hey, it is always good to have detailed message right?
Careful reader must already found the Assert method in the Mockery class, that is the one used to replace the Assert.IsTrue. It provides "better" message then true/false. The reason for the quotation marks around the word better is that at this moment, the implementation can sometime provide ambiguous message. Again this is just a POC, in order to provide accurate message, we will need to make deeper changes into the Rhino Mocks.
Below are some examples from the unit test:
Mockery.Assert(mb.ActivityOf(b => b.Bar()).First .OccuredBefore(ma.ActivityOf(a => a.Act()).First) .OccuredBefore(mb.ActivityOf(b => b.Bar()).Last)); (mb.ActivityOf(b => b.Bar()).First < ma.ActivityOf(a => a.Act()).First < mb.ActivityOf(b => b.Bar()).Last).AssertOccured();
Mockery.Assert(foo.ActivityOf(f => f.Foo(1)) | foo.ActivityOf(f => f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything))); (foo.ActivityOf(f => f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything)) | foo.ActivityOf(f => f.Foo(1))).AssertOccured();
C# 2.0 / VS2005 Support
It was said that the AAA for C# 2.0 is ugly and tedious. I tried to using this new API with C# 2.0 syntax. The result is not too bad at all. Let's take a look at what we have in the unit test cases:
Mockery.Assert(Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).Last .OccuredAfter(Mockery.ActivityOf(mb , delegate(IB b) { b.Bar(); }).Last) .OccuredAfter(Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).First)); (Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).Last > Mockery.ActivityOf(mb, delegate(IB b) { b.Bar(); }).Last > Mockery.ActivityOf(ma, delegate(IA a) { a.Act(); }).First).AssertOccured(); Mockery.Assert(
Mockery.ActivityOf(foo, delegate(IFoo f) { f.Foo(1); }) | Mockery.ActivityOf(foo, delegate(IFoo f) { f.Foo(Arg<int>.Is.Equal(1), Arg<int>.Is.Anything); }));
Give It A Try
If you want to give it a try, I have build a version of Rhino Mocks based on last trunk source with the latest Castle DynamicProxy2 at this moment. You can find them here:
http://code.google.com/p/kennethxublogsource/downloads/list
There are also other features in the build like:
- Create multi mock for AAA: Mockery.GenerateMultiMock
- Create partial mock for AAA: Mockery.GeneratePartialMock and Mocker.GeneratePartialMultiMock
Update (7/9/2009): For people don't want a modified version of Rhino.Mocks, I have made an extension library without the feature of ordered expectation: Some Utilities Extend Rhino.Mocks
1 comment:
I Honestly never liked this.
- ExtensionMethods?
I Dislike those since it for me takes out the focus on the test. And it blurs the test code more.
the thing about Assert.That (from NUnit) and Expect.Call is that the first thing on the line is the test followed by what you desire to do the test on. It makes it very explicit.
- AAA ?
While a noble idea, I do feel it's a bit double work when I have to both set up expectations as well as then asserting them after worth...
AAA is more use full for State testing in my eyes, where as Rhino mocks should focus on what it started to be, behaviour testing...
Post a Comment