views:

86

answers:

1

I'm having problems testing this scenario.

An invoice has two states - finished and unfinished - and I want to test that the method Presenter.FinishInvoice() calls DAO.FinishInvoice() then calls DAO.GetInvoice() and then sets View.Invoice with the result. The problem is that I need to call DAO.GetInvoice() to get an invoice to finish in the first place and this is called from Presenter.InitializeView() (tested in another test).

Here is my test:

using (mocks.Record())
{
    SetupResult.For(view.Invoice).PropertyBehavior();
    SetupResult.For(DAO.GetInvoice(1)).Return(invoice);
    Expect.Call(DAO.FinishInvoice(1)).Return(true);
    Expect.Call(DAO.GetInvoice(1)).Return(invoice);
}
using (mocks.Playback())
{
    Presenter presenter = new Presenter(view, DAO);
    presenter.InitializeView(1);
    presenter.FinishInvoice();
}

DAO.GetInvoice() will be called and View.Invoice set once when InitializeView() is called. It's not part of the test but FinishInvoice() will fail if I don't set View.Invoice to an unfinished invoice so the return value needs to be set.

The second call to DAO.GetInvoice() is called from FinishInvoice() and is part of the test.

If I run this test I get a fail on DAO.GetInvoice(1); Expected #1, Actual #0. I've stepped through the code and it does call DAO.GetInvoice() when FinishInvoice() is called so it must be my test code that is faulty, not my presenter code.

If I change:

    SetupResult.For(DAO.GetInvoice(1)).Return(invoice);

to:

    Expect.Call(DAO.GetInvoice(1)).Return(invoice);

it works but that shouldn't be part of the test as it is just needed for set up (but can't be put in the SetUp method as it's not required for all tests)

I suppose that it's not a disaster that I need to do it with Expect.Call() but I'd like to learn how to set it up how I want it to be.

A: 

Since you want to test the interaction of your DAO-class you need to create it as a mock and not as a stub. This means you can't use SetupResult for it.

If you don't care about the order of the method calls you can just use the Repeat-syntax:

using (mocks.Record())
{
    SetupResult.For(view.Invoice).PropertyBehavior();
    Expect.Call(DAO.FinishInvoice(1)).Return(true);
    Expect.Call(DAO.GetInvoice(1)).Return(invoice).Repeat.Any();
}
using (mocks.Playback())
{
    Presenter presenter = new Presenter(view, DAO);
    presenter.InitializeView(1);
    presenter.FinishInvoice();
}

If you do care about the order of the method calls you will have to specify each expectation explicitly with the Ordered-syntax:

using (mocks.Record())
{     
    SetupResult.For(view.Invoice).PropertyBehavior();

    using (mocks.Ordered())
    {  
       Expect.Call(DAO.GetInvoice(1)).Return(invoice);     
       Expect.Call(DAO.FinishInvoice(1)).Return(true);
       Expect.Call(DAO.GetInvoice(1)).Return(invoice);
    }
}
using (mocks.Playback())
{
    Presenter presenter = new Presenter(view, DAO);
    presenter.InitializeView(1);
    presenter.FinishInvoice();
}

However, if you're calling DAO.GetInvoice twice in yor code, I would say that's a code smell, and you should probably look into refactoring it to just one call.

Also, here is what this would look like with the AAA-syntax from 3.5:

//Arrange
DAO.Stub( x => x.GetInvoice(1) ).Return(true).Repeat.Any();

//Act
Presenter presenter = new Presenter(view, DAO);
presenter.InitializeView(1);
presenter.FinishInvoice();

//Assert
DAO.AssertWasCalled( x => x.FinishInvoice(1) );
DAO.AssertWasCalled( x=> x.GetInvoice(1) );

As you can see this is a lot nicer, and you can even use a mock as both a mock and a stub.

martinnjensen
The reason why GetInvoice() is called twice is because FinishInvoice() calls a stored procedure that will change the invoice and so you need to re-fetch it to show the changes. Also, this is just a test and doesn't show the timeline between the two calls to GetInvoice(). You load the invoice, view it, change it, finish it and then re-load it to view the changes that finishing it might have made.In my test code, both the view and the dao are mocks - I wasn't aware that you couldn't use SetupResult.For() on mocks. I'll do more reading up on that topic. Thanks.