Where mocking fails

time to read 2 min | 356 words

I mentioned before that Rhino Mocks is a very powerful framework. This can be a problem at times, because it make it hard to notice that you cross the line into mock abuse.

Let us look at this test for example:

[Test]
public void Will_raise_message_arrived_event_for_each_message_in_batch_and_across_batches()
{
	var stubbedIncomingMessageRepository = MockRepository.GenerateStub<IIncomingMessageRepository>();
	var queue = new Queue(new Uri("queue://localhost/testQueue"),
						  MockRepository.GenerateStub<IOutgoingMessageRepository>(),
						  stubbedIncomingMessageRepository);
	var msgQueue = new Queue<QueueMessage>();

	stubbedIncomingMessageRepository
		.Stub(x => x.GetEarliestMessage())
		.Return(null)
		.Do(invocation =>
		{
			lock (msgQueue)
			{
				invocation.ReturnValue = msgQueue.Count == 0 ? null : msgQueue.Dequeue();
			}
		})
		.Repeat.Any();
	stubbedIncomingMessageRepository
		.Stub(x => x.Transaction(Arg<Action>.Is.Anything))
		.Do(invocation => ((Action) invocation.Arguments[0])())
		.Repeat.Any(); ;
	stubbedIncomingMessageRepository
		.Stub(x => x.Save(Arg<QueueMessage>.Is.Anything))
		.Do(invocation =>
		{
			lock (msgQueue)
			{
				msgQueue.Enqueue((QueueMessage)invocation.Arguments[0]);
			}
		})
		.Repeat.Any();

	var callCount = 0;
	var e = new ManualResetEvent(false);
	queue.MessageArrived += (obj =>
	{
		if (Interlocked.Increment(ref callCount) >= 100)
			e.Set();
	});
	for (int i = 0; i < 50; i++)
	{
		queue.AcceptMessages(
			new QueueMessage(), 
			new QueueMessage()
			);
	}
	e.WaitOne();
	Assert.AreEqual(100, callCount);
}

Here we are using Rhino Mocks to fake the entire behavior of the type. Note the thread handling in the mock object, yuck! A large portion of the test is implementing a fake message queue, in a non intuitive way.

Let us explore a better way:

[Test]
public void Will_raise_message_arrived_event_for_each_message_in_batch_and_across_batches()
{
	var queue = new Queue(new Uri("queue://localhost/testQueue"),
						  MockRepository.GenerateStub<IOutgoingMessageRepository>(),
						  new FakeIncomingMessageRepository());

	var callCount = 0;
	var e = new ManualResetEvent(false);
	queue.MessageArrived += (obj =>
	{
		if (Interlocked.Increment(ref callCount) >= 100)
			e.Set();
	});
	for (int i = 0; i < 50; i++)
	{
		queue.AcceptMessages(
			new QueueMessage(), 
			new QueueMessage()
			);
	}
	e.WaitOne();
	Assert.AreEqual(100, callCount);
}

public class FakeIncomingMessageRepository : IIncomingMessageRepository
{
	readonly Queue<QueueMessage> msgQueue = new Queue<QueueMessage>();

	public QueueMessage GetEarliestMessage()
	{
		lock (msgQueue)
		{
			return msgQueue.Count == 0 ? null : msgQueue.Dequeue();
		}
	}

	public void Save(QueueMessage msg)
	{
		lock(msgQueue)
		{
			msgQueue.Enqueue(msg);
		}
	}

	public void Transaction(Action action)
	{
		action();
	}
}

Here we handed coded the fake object, and used that in the test. The reduction in complexity is quite significant.

So, when should you avoid mocks? When there is more pain in using them than not.

Not a helpful metric, but that is how I do it.