We’re currently working on a custom Work Flow system (no, not Windows Work Flow) that utilizes MSMQ. To make each work flow node highly testable what I decided to do was create an abstraction of the items I needed from the System.Messaging.MessageQueue class. This abstraction is an interface named IMessageQueue. The next thing was to create a “QueueServer” class that obtains an instance of IMessageQueue via a simple Inversion of Control container named QueueResolver.
QueueServer Start method:
public void Start()
{
Queue = QueueResolver.Current.CreateInstance(Path, Transactional);
if (Transactional)
{
Queue.PeekCompleted += PeekCompleted;
Queue.BeginPeek();
}
else
{
Queue.ReceiveCompleted += ReceiveCompleted;
Queue.BeginReceive();
}
}
The default implementation of the QueueResolver class is quite simple (yes, i know we could use any of the common IOC containers to achieve this – but I like this approach for some specific situations since it is more lightweight and I don’t have to worry about maintaining a .config file, etc):
public class QueueResolver
{
static QueueResolver()
{
Current = new QueueResolver();
}
public static void Initialize(QueueResolver resolver)
{
Validate.NotNull("resolver", resolver);
Current = resolver;
}
public static QueueResolver Current
{
get;
private set;
}
protected QueueResolver()
{
}
public virtual IQueueClient< T > CreateClient< T >(string name, string path, bool transactional)
where T : class, IFlowObject< long >
{
return new QueueClient< T >(name, path, transactional);
}
public virtual IQueueServer< T > CreateServer< T >(string name, string path, bool transactional)
where T : class, IFlowObject< long >
{
return new QueueServer< T >(name, path, transactional);
}
public virtual IMessageQueue CreateInstance(string path, bool transactional)
{
Validate.NotNullOrEmpty("path", ref path);
MessageQueue queue;
if (MessageQueue.Exists(path))
{
queue = new MessageQueue(path);
}
else
{
queue = MessageQueue.Create(path, transactional);
}
queue.Formatter = new BinaryMessageFormatter();
return new MessageQueueProxy(queue);
}
}
This ends up being a very simple way for me to break the dependency on an actual MSMQ instance and allows me to inject test doubles of IMessageQueue with some simple code like:
const string Name = "Test Queue";
const string Path = @".\Private$\TestQueue";
const bool Transactional = true;
var resolver = MockRepository.GenerateStub< QueueResolver >();
var queue = MockRepository.GenerateMock< IMessageQueue >();
resolver.Stub(r => r.CreateInstance(Path, Transactional)).Return(queue);
QueueResolver.Initialize(resolver);
The advantage to all this, of course, is that I can now use Rhino Mocks to test the inner functionality of the QueueServer by utilizing this Dependency Injection technique:
bool eventRaised = false;
var customer = MockRepository.GenerateStub< ICustomer >();
var server = new QueueServer< ICustomer >(Name, Path, Transactional);
server.ItemReceived += delegate(object sender, QueueEventArgs< ICustomer > e)
{
eventRaised = true;
Assert.AreSame(e.Item, customer);
};
server.Start();
var message = new Message();
message.Body = customer;
queue.Raise(q => q.PeekCompleted += null, queue, new EventArgs< message >(message));
Assert.IsTrue(eventRaised);
We also have a QueueClient class that obtains an instance of IMessageQueue in the same manner. With these two classes we will be able to easily model our work flow and completely unit test the interaction between the queues. Also we will be able to easily unit test the entire lifespan of a “flow object” and write robust unit tests to track it throughout the work flow.