Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,972 | Comments: 44,521

filter by tags archive

Where mocking fails


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.


Comments

Shawn Neal

Hand coded mocks or stubs are also the way to go if you find yourself re-using that particular fake object in multiple tests and/or fixtures. You could use RhinoMocks in a common setup method, but I often find in these cases there are just too many methods or interactions to warrant mocking in a meaningful manner, especially when Re# will quite quickly produce an implementation of an interface. Perhaps this is just a code smell?

I generally find that hand coded fake objects are easier to deal with and understand when you have more than just a few lines of code to setup a mock, like in your example. After all, not everything is a nail.

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

  1. Reducing parsing costs in RavenDB - 2 hours from now

There are posts all the way to Aug 04, 2015

RECENT SERIES

  1. Production postmortem (5):
    29 Jul 2015 - The evil licensing code
  2. Career planning (6):
    24 Jul 2015 - The immortal choices aren't
  3. API Design (7):
    20 Jul 2015 - We’ll let the users sort it out
  4. What is new in RavenDB 3.5 (3):
    15 Jul 2015 - Exploring data in the dark
  5. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats