Why AAA style mocking is better than Record / Playback

by Rasmus Kromann-Larsen May 24, 2009 21:06

If you've followed me on twitter for more than a couple of days, you will most probably have heard my grumbling each time I run into issues using record / playback mocking - so I thought I'd write a short post on my experiences with both and why I think that I keep bumping into issues with record / playback.

Phases of a test

If you take a look at the normal flow of a test method without mocking, it will usually perform some kind of setup, then perform some action that invokes the code under test - and then at the end, make some assertions about the state of some component that you want verified. While some tests continue after this point, this is where you should stop if you're following the "one assert per test" rule - this is sort of the single responsibility principle for tests. This flow is also the inspiration for the AAA name - first you Arrange your test setup, then you Act upon the class under test - and then you Assert something about the state. Here's a simple example without mocking:

[Test]
public void CanRemoveCategories()
{
    // Arrange
    var collection = new CategoryCollection("test");

    // Act
    collection.RemoveCategory("test");

    // Assert
    Assert.That(collection.Count, Is.EqualTo(0));
}

This test is chronologically sound, it makes sense and it is easy to read - but then again, this is a state-based test, I said I was going to talk about behavioral tests - mock tests.

Phase confusion

Many people find that mocking is rather difficult to understand and that it often very hard to read and understand. Since our tests act as an API-description for our code - and since we want to be able to figure out how to fix our failed tests - readability is important. Now, let's look at one of my tests from a Record / Playback point of view. I'm using Rhino Mocks as my mocking framework in this test:

[Test]
public void ShouldIgnoreRespondentsThatDoesNotExistRecordPlayback()
{
    var guid = Guid.NewGuid();
    IEventRaiser executeRaiser;

    using(_mocks.Record())
    {
        Expect.Call(_view.Respondents).Return(new[] {guid.ToString()});
        Expect.Call(_repository.GetById(guid)).Return(null);

        _view.ExecuteOperation += null;
        executeRaiser = LastCall.IgnoreArguments()
            .Repeat.Any()
            .GetEventRaiser();

        Expect.Call(_view.OperationErrors = null)
            .IgnoreArguments()
            .Constraints(List.IsIn("Non-existant respondent: " + guid));
    }

    using(_mocks.Playback())
    {
        new BulkRespondentPresenter(_view, _repository);
        executeRaiser.Raise(null, EventArgs.Empty);
    }
}

Now, at a glance, can you tell me what this test is really doing? There's something with a view and a repository and we can probably deduce quite a bit from the test name. But it's rather hard to separate the different phases I talked about before Arrange, Act and Assert. Below, I've tried to annotate the test with the phases:

[Test]
 public void ShouldIgnoreRespondentsThatDoesNotExistRecordPlayback()
 {
     // Arrange
     var guid = Guid.NewGuid();
     // Part of Act
     IEventRaiser executeRaiser;

     using(_mocks.Record())
     {
         // Arrange (or Assert?)
         Expect.Call(_view.Respondents).Return(new[] {guid.ToString()});
         Expect.Call(_repository.GetById(guid)).Return(null);

         // Part of Act
         _view.ExecuteOperation += null;
         executeRaiser = LastCall.IgnoreArguments()
             .Repeat.Any()
             .GetEventRaiser();

         // Assert
         Expect.Call(_view.OperationErrors = null)
             .IgnoreArguments()
             .Constraints(List.IsIn("Non-existant respondent: " + guid));
     }

     using(_mocks.Playback())
     {
         // Arrange
         new BulkRespondentPresenter(_view, _repository);
         // Act
         executeRaiser.Raise(null, EventArgs.Empty);
     }
 }

No wonder it's hard to read and understand. The phases are mixed all over - and the Asserts are in the middle of the test - this is nothing like the natural flow of the previous test without mocking. I usually like to have the phases separated in my test with comments as well, but it's just not possible in this test. I wrote this test up rather quickly, so there might be a better way of doing it that I am missing - if there is, please yell at me :-)

Sorting out the confusion

AAA mocking, as the name suggests, is all about clearing out the confusion in that last test - it's about maintaining the original test flow. It just so happens also to have some other benefits, that I will get into later in the post. I've written the same test as above in an AAA style, this time with Moq, since I'm trying it out at the moment, but Rhino Mocks has similar syntax. Moq is pretty heavy on lambda expressions, but even if you haven't worked with those yet, I'm sure you will grasp the idea. If you want a general introduction to mocking with Moq, Justin Etheredge has a small series about it.

[Test]
public void ShouldIgnoreRespondentsThatDoesNotExist()
{
    // Arrange
    var guid = Guid.NewGuid();
    _viewMock.Setup(x => x.Respondents).Returns(new[] { guid.ToString() });
    _repositoryMock.Setup(x => x.GetById(guid)).Returns(() => null);

    // Act
    _viewMock.Raise(x => x.ExecuteOperation += null, EventArgs.Empty);

    // Assert
    _viewMock.VerifySet(x => x.OperationErrors =
        It.Is<IList<string>>(l => l.Contains("Non-existant respondent: "+guid)));
}

Those comments are actually in my original test as well - and in my test live template I generate all my tests with. If you compare this test to the one above, you will see that it has more or less the same components, but this time, they're arranged in a way that makes sense for the next reader of the test. The fact that the test is shorter is also slightly unfair, since my first test used an event raiser, which involves "many" lines of code. Also the separation of the phases allowed me to move the actual construction of the presenter our of the actual test and into shared setup code.

So what techniques did AAA mocking introduce to help alleviate the pains? First of all, the mocks no longer has states - that's what record and playback really refer to: A mock in record state will record calls made on it and then expect them to be called again during the playback state. Furthermore, it cleanly separates mock setup from mock expectations.

What is gained?

So what did we gain with AAA style mocking over the traditional record / playback style?

  • The main selling point for me is readability and test simplicity - it is much easier for me to explain mocking to someone else with AAA.
  • If you have done any fairly advanced record / playback mocking, you will find that when the mocks have states, it will often result in subtle test failures.
  • Clean separation of test phases.
  • Greatly improved ability to move shared code out of tests. Since you have the first part of your test handling setup, extending this part to start before the actual test (in a setup method) is no problem. With record / playback mocking, you will often run into state failures if you attempt it.

kick it on DotNetKicks.com

Tags:

Development | Testing

Comments

5/25/2009 8:36:04 PM #

Thomas Jespersen

Hi Rasmus

Nice post... i guess Wink

You realy make me feel stupid. Next time I see you, you must explain excactly what goes on here.

* Which type is x in the 4 lamda expressions?
* You set up _repositoryMock.Setup expecting a call to GetById()... but you never call it!
* Excactly what does _viewMock.Raise(..) in the act part do?  
* Finally: in your assert, you call _viewMock.VerifySet(...) to ensure that x.OperationErrors contains a IList<string>, which contains an entry with a "magic string".

Maybe if you included more code I could see it. I'm still trying to figure out which type ExecuteOperation and OperationErrors belongs to? And does x.Respondents belongs to a RespondentRepository... or is it the repository?

Thomas Jespersen Denmark

5/25/2009 9:02:49 PM #

Rasmus Kromann-Larsen

I'm sorry - I didn't go into the details of the mocking framework itself - and yes, the code being tested is not included in the post...

I will be happy to explain it to you though Smile Or you can read Justin's posts on Moq.


Rasmus Kromann-Larsen Denmark

5/25/2009 9:05:25 PM #

Rasmus Kromann-Larsen

The intent was to show the difference in the styles of mocking Smile

I'll write some more basic mock posts if you'd like.

Rasmus Kromann-Larsen Denmark

6/4/2009 2:50:48 PM #

Jakob Christensen (t4rzsan)

I kind of like the Record/Playback syntax of Rhino Mocks but I see your point.

However, some would argue that the Setup calls with Moq (which are similar to Expect with Rhino Mocks) are actually asserts which means that you are asserting in your Arrange block.  Any ideas on how this can be avoided?

Jakob Christensen (t4rzsan) Denmark

6/4/2009 3:59:59 PM #

Rasmus Kromann-Larsen

Just to be clear - Rhino.Mocks has a very similar AAA syntax as an alternative to RP - so this is not particularly Moq specific.

My setup calls in Moq are infact arranging - I am arranging for certain things to be returned when specific methods are called - I am arranging the environment for my tests. This is not to be mistaken for asserts, my tests will not fail if these are not called.

One of the problems with RP is that it is very difficult to keep these two concepts seperated.

Rasmus Kromann-Larsen Denmark

6/5/2009 2:49:09 PM #

Jakob Christensen

I am not talking about the return values that you arrange in your setup calls.

My point is that when you do your setup calls you are actually creating expectations for the parameters being passed to the mocked objects.  Those expectations are similar to Asserts.  Your example is very simple so in this case there is no problem.

Where would be the right way to assert on parameters passed to the mocked objects?

Jakob Christensen Denmark

6/5/2009 2:59:24 PM #

Jakob Christensen

Just found out that version 3.5 of Rhino Mocks actually solves it.  Here's an example:
kashfarooq.wordpress.com/.../

Smile Jakob.

Jakob Christensen Denmark

6/5/2009 6:54:10 PM #

Rasmus Kromann-Larsen

Well, I'm not sure I understand what you mean, I can't see anything in that post that isn't possible in Moq as well.

In my last assert, I am also verifying the arguments of the expectation. (look at the It.Is syntax)

In my setup, I can do similar pattern matching on the arguments to decide how my environment should react - but in this simple cast it's enough to give it the actual values.

Rasmus Kromann-Larsen Denmark

9/20/2009 10:34:32 PM #

trackback

Trackback from NHibernate blog

Part 10: Testing and Refactoring

NHibernate blog

9/20/2009 10:35:19 PM #

pingback

Pingback from jasondentler.com

Part 10: Testing and Refactoring «  BASICly everything

jasondentler.com

5/22/2010 12:55:08 PM #

pingback

Pingback from 196.cmanager.org

Gm K15 K1500 Pickup Bulb, B150 Aftermarket Best Toyota

196.cmanager.org

8/23/2010 3:21:26 PM #

pingback

Pingback from grillrestaurant.interactiveinfonet.info

Grill restaurant - Mountain restaurant - Fire grill restaurant

grillrestaurant.interactiveinfonet.info

Comments are closed

Powered by BlogEngine.NET 1.6.1.0
Theme by Mads Kristensen | Modified by Mooglegiant | Adjusted by Rasmus Kromann-Larsen

About Me

I am a danish .NET developer blogging about the technical side of my life, mostly .NET stuff, but also fundamental topics like design patterns, principles and productivity boosters.

In addition, I am a core group member of Aarhus .NET User Group.