I face quite often the need to test a composite pattern variations. And truly was duplicating some similar code around. As tests are now my focus for self-improvement (and always, should I be eaten alive by the tdd purists if I lie), I decided to extract some of the snippets into reusable helper methods.
Here is what I got for the calling of the composite pattern "operation":
The usage:
CompositePatternTestHelper.TestForCompositeOperation<ProcessCoordinator, IProcess>(
parent => parent.Stop(), // Parent operation
child => child.Stop(), // Should invoke following on every child
(parent, child) => parent.Processes.Add(child) // How to add add child to the parent
);
And for the helper method/class implementation (The most recent version available at http://code.google.com/p/toolsdotnet/source/browse/trunk/Tools.Net/src/Tools.Tests.Helpers/CompositePatternTestHelper.cs):
using System;
using Rhino.Mocks;
namespace Tools.Tests.Helpers
{
public static class CompositePatternTestHelper
{
/// <summary>
/// Helper method to test composite [parent/child] pattern implementation, where calls
/// to the parent result into calls onto its children.
/// </summary>
/// <remarks>Creates the parent object using its default constructor</remarks>
public static void TestForCompositeOperation<ParentType, ChildType>(Action<ParentType> parentAction, Action<ChildType> childAction, Action<ParentType, ChildType> addChild)
where ChildType : class
where ParentType : new()
{
// Requires a default ctor to exists
var parent = new ParentType();
TestForCompositeOperation(parent, parentAction, childAction, addChild);
}
/// <summary>
/// Helper method to test composite [parent/child] pattern implementation, where calls
/// to the parent result into calls onto its children.
/// </summary>
/// <remarks>Uses the passed in instance of a parent</remarks>
public static void TestForCompositeOperation<ParentType, ChildType>(ParentType parent,
Action<ParentType> parentAction, Action<ChildType> childAction, Action<ParentType, ChildType> addChild)
where ChildType : class
{
// Use Rhino.Mocks to create stubs
var child1 = MockRepository.GenerateStub<ChildType>();
var child2 = MockRepository.GenerateStub<ChildType>();
// Setup two children, the arbitrary choice, but should not really matter
child1.Expect(childAction);
child1.Expect(childAction);
// Add children to the parent
addChild(parent, child1);
addChild(parent, child1);
// Call parent action
parentAction(parent);
// Assert parent action resulted in the calls to children
child1.AssertWasCalled(childAction);
child1.AssertWasCalled(childAction);
}
}
}
7 comments:
Hi Stan, this is indeed a great post addressing a common pain. I have 2 things to point out though (I know this is a self note, but just to start a design discussion):
1 -
// Requires a default ctor to exists
var parent = new ParentType();
This could be supported by another helper which gets parent as a parameter, because for me most of the cases parent does not have a default ctor. It either gets a child, another parent instance, or the basic interface / abstract class instance to work on.
By refactoring this way we wouldn't be violating SRP principle too, since we wouldn't be intrusive on how consumer creates the parent.
2 - Since you are setting an expectation, wouldn't it be better to use GenerateMock instead of stubs ?
Thanks again, the idea is really great. Certainly goes into my common codebase ! :)
Oh man,
do you ever sleep :)? There is a method overload just bellow the one you pointed out :). As for using GenerateStub - I'm just trying the new syntax from Rhino.Mocks as you can see at the bottom of the second method, that is more for AAA (Arrange, Act, Assert). I'm truly on the one boat with Roy Osherov on that, that it is probably a good time to forget Mock, Stub, Test Double.
I see, you probably use some offline rss reader, it looks like you commented on something that was my first draft to publish, btw.
Ever considered Google reader? :).
Haha :) Actually I am using Google Reader, I don't know why it didn't refresh. I am having lots of bad experience with Google Reader nowadays, and am seriously considering to go back to FeedDemon :) Apologies on that.
On Stubs - Well, for the newbies it matters, but for us who already knows the meaning, we can talk about it can't we ? :)
Look here http://ayende.com/Blog/archive/2008/05/16/Rhino-Mocks--Arrange-Act-Assert-Syntax.aspx. That is what I'm teasing around with :).
:) i knew what you mean, but if you look that post there is no expectation on stubs. I think there was another discussion in another post that to even in the next versions, throw an exception to avoid to set expectations on stubs.
aha, I found it during writing : http://ayende.com/Blog/archive/2008/06/29/Rhino-Mocks-3.5-Design-Decisions-To-be-strict-or-not.aspx
Good link btw. The point is once again, I'm with Roy Osherove on this one (http://weblogs.asp.net/rosherove/archive/2008/09/20/goodbye-mocks-farewell-stubs.aspx). So I'm exploring where AAA pushes Ayende at the moment :):). It looks like issues he is facing now with semantics is what Roy has resolved with just more practical view.
Post a Comment