Interaction Tests with Mocks

I seem to always be working on large, unwieldy WebForms apps. Because I am test infected, this means I often find myself refactoring large amounts of code out of code behind classes into Presenters a la Model-View-Presenter so that I can unit test the Application logic as the first step toward a proper Domain model, separation of concerns and, eventually, MVC. Because the only way to know that most of these pages is working is by looking at the UI, I use interaction style tests to verify that the UI is displaying the correct data. Mocks and Stubs become crucial elements of my tests. I used to use Rhino Mocks, but for nearly a year, I have been using Moq. Overall, I prefer Moq, though I don't have any specific reasons why.

Here is a (somewhat contrived) example of how I have been writing these tests using Rhino Mocks:

[Test]
public void HandlePageLoad_displays_client_full_name()
{
var clientRepositoryMock = MockRepository.GenerateMock<IClienRepository>();
var viewMock = MockRepository.GenerateMock<IView>();

var client = new Client { FirstName = "First", LastName = "Last" };

clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
viewMock.Expect(x => x.DisplayFullName("First Last"));

new ClientPresenter(clientRepositoryMock , viewMock).HandlePageLoad();

viewMock.VerifyAllExpectations();
}

Add a second test...

[Test]
public void HandlePageLoad_displays_client_phone()
{
var clientRepositoryMock = MockRepository.GenerateMock<IClienRepository>();
var viewMock = MockRepository.GenerateMock<IView>();

var client = new Client { Phone = new PhoneNumber(800, 555, 1212, "00000") };

clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
viewMock.Expect(x => x.DisplayPhoneNumber(client.Phone.ToString()));

new ClientPresenter(clientRepositoryMock, viewMock).HandlePageLoad();

viewMock.VerifyAllExpectations();
}

Of course, now I see duplication, so I want to refactor:

IClienRepository clientRepositoryMock;
IView viewMock;
ClientPresenter presenter;

[SetUp]
public void SetUp()
{
clientRepositoryMock = MockRepository.GenerateMock<IClienRepository>();
viewMock = MockRepository.GenerateMock<IView>();
presenter = new ClientPresenter(clientRepositoryMock, viewMock);
}

[TearDown]
public void TearDown()
{
viewMock.VerifyAllExpectations();
clientRepositoryMock.VerifyAllExpectations();
}

[Test]
public void HandlePageLoad_displays_client_full_name()
{
var client = new Client { FirstName = "First", LastName = "Last" };

clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
viewMock.Expect(x => x.DisplayFullName("First Last"));

presenter.HandlePageLoad();
}

[Test]
public void HandlePageLoad_displays_client_phone()
{
var client = new Client { Phone = new PhoneNumber(800, 555, 1212, "00000") };

clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
viewMock.Expect(x => x.DisplayPhoneNumber(client.Phone.ToString()));

presenter.HandlePageLoad();
}

I might add a bunch of other tests after that, and they would be short and expressive like these two.

So I have taken that form and translated it directly in to my Moq use.

Mock<IClienRepository> clientRepositoryMock;
Mock<IView> viewMock;
ClientPresenter presenter;

[SetUp]
public void SetUp()
{
clientRepositoryMock = new Mock<IClienRepository>();
viewMock = new Mock<IView>();
presenter = new ClientPresenter(clientRepositoryMock.Object, viewMock.Object);
}

[TearDown]
public void TearDown()
{
viewMock.VerifyAll();
clientRepositoryMock.VerifyAll();
}

[Test]
public void HandlePageLoad_displays_client_full_name()
{
var client = new Client { FirstName = "First", LastName = "Last" };

clientRepositoryMock.Setup(x => x.GetClientId(It.IsAny<int>())).Return(client);
viewMock.Setup(x => x.DisplayFullName("First Last")).Verifiable();

presenter.HandlePageLoad();
}

[Test]
public void HandlePageLoad_displays_client_phone()
{
var client = new Client { Phone = new PhoneNumber(800, 555, 1212, "00000") };

clientRepositoryMock.Setup(x => x.GetClientId(It.IsAny<int>())).Return(client);
viewMock.Setup(x => x.DisplayPhoneNumber(client.Phone.ToString())).Verifiable();

presenter.HandlePageLoad();
}

While pairing the other day, we started discussing that the intent of the TearDown method is to undo any setup what was put in place before the test, NOT to assert anything. The VerifyAll() call is effectively an assertion that the mocks were interacted with properly. This it what was recommended instead:

Mock<IClienRepository> clientRepositoryMock;
Mock<IView> viewMock;
ClientPresenter presenter;

[SetUp]
public void SetUp()
{
clientRepositoryMock = new Mock<IClienRepository>();
viewMock = new Mock<IView>();
presenter = new ClientPresenter(clientRepositoryMock.Object, viewMock.Object);
}

[Test]
public void HandlePageLoad_displays_client_full_name()
{
var client = new Client { FirstName = "First", LastName = "Last" };

clientRepositoryMock.Setup(x => x.GetClientId(It.IsAny<int>())).Return(client);

presenter.HandlePageLoad();

viewMock.Verify(x => x.DisplayFullName("First Last"));
}

[Test]
public void HandlePageLoad_displays_client_phone()
{
var client = new Client { Phone = new PhoneNumber(800, 555, 1212, "00000") };

clientRepositoryMock.Setup(x => x.GetClientId(It.IsAny<int>())).Return(client);

presenter.HandlePageLoad();

viewMock.Verify(x => x.DisplayPhoneNumber(client.Phone.ToString()));
}

I have to admit that this is a much more Arrange-Act-Assert manner of writing tests with mock objects. It also seems to be the intent of the Moq library. I think my Playback-Record experience with early mocking tools was holding me back from seeing this way of writing my tests. I am going to try it for a while and report my experience...

Comments

  1. Interesting thought on using verify instead of expect. It seems like a more natural way of expressing the purpose of the test and I would think that it would make a lot more sense to people learning mock testing. I think one of the biggest challenges with learning mock testing is understanding that the expects are not actually doing anything at the point that they are hit in the code. I'll be curious to hear your experience, I wonder if there are any advantages to expects over verify. BTW, I think you can do the same with RhinoMocks using AssertWasCalled.

    ReplyDelete
  2. +1 on Verify. These are assertions and they belong in the Assert section when you're doing AAA, IMHO.

    ReplyDelete
  3. The Expect/Verify is the older record/playback syntax. I'd be more inclined to use the newer Arrange/Act/Assert model that's also available with Rhino Mocks.

    IClientRepository clientRepositoryMock;
    IView viewMock;
    ClientPresenter presenter;
     
    [SetUp]
    public void SetUp()
    {
     clientRepositoryMock = MockRepository.GenerateMock();
     viewMock = MockRepository.GenerateMock();
     presenter = new ClientPresenter(clientRepositoryMock, viewMock);
    }
     
    [Test]
    public void HandlePageLoad_displays_client_full_name()
    {
     var client = new Client { FirstName = "First", LastName = "Last" };
     
     clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
     
     presenter.HandlePageLoad();

     viewMock.AssertWasCalled(x => x.DisplayFullName("First Last"));
    }
     
    [Test]
    public void HandlePageLoad_displays_client_phone()
    {
     var client = new Client { Phone = new PhoneNumber(800, 555, 1212, "00000") };
     
     clientRepositoryMock.Stub(x => x.GetClientId(0)).IgnoreArguments().Return(client);
     
     presenter.HandlePageLoad();

     viewMock.AssertWasCalled(x => x.DisplayPhoneNumber(client.Phone.ToString()));
    }

    ReplyDelete
  4. So it seems that the only people who are using Rhino Mocks the way I describe at the beginning of the post are those of us you learned mocks using early versions of the tool and never updated our understanding as the tooling evolved and improved. I'm glad someone finally pointed out my lack of knowledge. Isn't pair programming awesome? :-)

    ReplyDelete

Post a Comment

Popular posts from this blog

TFS to SVN Conversion with History

TDD vs BDD

System Architecture: Bounded Contexts