views:

227

answers:

6

First let me state that, despite being a fairly new practitioner of TDD, I'm pretty much sold on its benefits. I feel like I've progressed enough to consider using mocks and have hit a real brick wall when it comes to understanding where mocks fit in with OOP.

I've read as many relevant posts/articles on the subject as I could find (Fowler, Miller) and am still not totally clear on how or when to mock.

Let me give a concrete example. My app has a service layer class (some call it an application layer?) in which methods map roughly to specific use cases. These classes may collaborate with the persistence layer, the domain layer and even other service classes. I've been a good little DI boy and have properly factored out my dependencies so they can be subbed for testing purposes etc.

A sample service class might look like this:

public class AddDocumentEventService : IAddDocumentEventService
{
    public IDocumentDao DocumentDao
    {
        get { return _documentDao; }
        set { _documentDao = value; }
    }
    public IPatientSnapshotService PatientSnapshotService
    {
        get { return _patientSnapshotService; }
        set { _patientSnapshotService = value; }
    }

    public TransactionResponse AddEvent(EventSection eventSection)
    {
        TransactionResponse response = new TransactionResponse();
        response.Successful = false;

        if (eventSection.IsValid(response.ValidationErrors))
        {

            DocumentDao.SaveNewEvent( eventSection,  docDataID);

            int patientAccountId = DocumentDao.GetPatientAccountIdForDocument(docDataID);
            int patientSnapshotId =PatientSnapshotService.SaveEventSnapshot(patientAccountId, eventSection.EventId);

            if (patientSnapshotId == 0)
            {
                throw new Exception("Unable to save Patient Snapshot!");
            }

            response.Successful = true;
        }
        return response;
    }

}

I went through the process of testing this method in isolation of its dependencies (DocumentDao, PatientSnapshotService) by using NMock. Here's what the test looks like

 [Test]
 public void AddEvent()
    {
        Mockery mocks = new Mockery();
        IAddDocumentEventService service = new AddDocumentEventService();
        IDocumentDao mockDocumentDao = mocks.NewMock<IDocumentDao>();
        IPatientSnapshotService mockPatientSnapshot = mocks.NewMock<IPatientSnapshotService>();

        EventSection eventSection = new EventSection();

        //set up our mock expectations
        Expect.Once.On(mockDocumentDao).Method("GetPatientAccountIdForDocument").WithAnyArguments();
        Expect.Once.On(mockPatientSnapshot).Method("SaveEventSnapshot").WithAnyArguments();
        Expect.Once.On(mockDocumentDao).Method("SaveNewEvent").WithAnyArguments();

        //pass in our mocks as dependencies to the class under test
        ((AddDocumentEventService)service).DocumentDao = mockDocumentDao;
        ((AddDocumentEventService)service).PatientSnapshotService = mockPatientSnapshot;

        //call the method under test
        service.AddEvent(eventSection);

        //verify that all expectations have been met
        mocks.VerifyAllExpectationsHaveBeenMet();
    }

My thoughts on this little foray into mocking are as follows:

  1. This test appears to break many fundamental OO precepts, not least of which is encapsulation: My test is thoroughly aware of the specific implementation details of the class under test (i.e method calls). I see alot of unproductive time spent updating tests whenever class internals change.
  2. Maybe its because my service classes are fairly simplistic at the moment, but I can't quite see what value these tests add. Is it that I'm guaranteeing that collaborating objects are being invoked as the specific use case dictates? The code duplication seems absurdly high for such a small benefit.

What am I missing?

+2  A: 

You mentioned a very good post from martin fowler on the subject. One point that he mentions is that mockists are the ones who likes to test behavior, and isolate stuff.

"The classical TDD style is to use real objects if possible and a double if it's awkward to use the real thing. So a classical TDDer would use a real warehouse and a double for the mail service. The kind of double doesn't really matter that much.

A mockist TDD practitioner, however, will always use a mock for any object with interesting behavior. In this case for both the warehouse and the mail service."

If you don't like this kind of stuff, you're probably a classical TDDer, and should use mocks only when it's awkward (like a mail service, or charging a credit card). Otherwise, you create your own doubles (like creating an in-memory database).

Particularly, I'm a mockist, but I don't verify much if a specific method is being called (unless it doesn't return values). In any case, I'd be testing to interfaces. When the function returns something, I use the mocking framework to create stubs.

At the end, it all comes in what and how you want to test. Do you think it is important to check if those methods were really called (use mocks)? Do you want to just check the state before and after the call (use fakes)? Pick what is enough to consider it is working, and then build your tests to check exactly that!

About the value of tests, I have some opinions:

  • On short term, when you TDD you usually get a better design, though you might take longer.
  • On long term, you won't be too afraid to change and maintain this code later (when you won't remember really well the details), and you'll get a red immediately, almost instant feedback.

By the way, it's normal for the test code size to be as big as production code size.

Samuel Carrijo
"[snip]I don't verify much if a specific method is being called (unless it doesn't return values). In any case, I'd be testing to interfaces.[snip]"I guess I didn't think that stubbing/mocking the objects invoked by the method under test was optional. For example, in the sample scenario I provided above, if I don't provide either stubs or real implementations for the colloborating objects, won't the test fail miserably?
Dirk
A: 

I have found it useful to have a fake(in memory) persistence layer for these types of tests; Then instead of verifying that certain calls were made, you can verify the end result(that the items now exist in the repository). I know you're working on using mocks, but I suppose I'm saying I don't think this is the best place to be doing it.

As an example, pseudocode for this test I would see as:

Instantiate the fake repositories.
Run your test method.
Check the fake repository to see if the new elements exist in it.

This keeps your tests unaware of the implementation specifics. However, it does mean maintaining the fake persistence layer, so I suppose it is a trade-off you'd have to consider.

Chris Shaffer
+1  A: 

Breaking encapsulation and thus making your tests more tightly coupled to your code can definitely be a disadvantage of using mocks. You don't want your tests to be brittle against refactoring. This is a fine line on which you have to walk. I personally avoid using mocks unless it is really hard, awkward, or slow otherwise. Looking at your code, first, I would use the BDD style: your test method should test a specific behavior of the method and should be named as such(maybe something like AddEventShouldSaveASnapshot). Second, a rule of thumb is to only verify that the expected behavior happened, rather than to catalogue every single method call that should have happened.

toby
Sounds intriguing. How would I verify the expected behavior in the sample I included without providing mocks for the object's colloboraters? In the same way that Chris describes in the answer below?
Dirk
A: 

It's sometimes worthwhile to separate the stakeholders in your code.

Encapsulation is about minimising the number of potential dependencies with the highest probability of change propagation. This is based on static source code.

Unit testing is about ensuring that the run-time behaviour does not change unintentionally: it is not based on static source code.

It's sometimes useful when the unit testers work not towards the original, encapsulated source code, but on a copy of the source code, which has all its private accessors automatically changed to public accessors (this is just a four-line shell script).

This cleanly separates encapsuation from unit testing.

There then is only left how low you go in your unit testing: how many methods do you want to test. This is a matter of taste.

For more on encapsulation (but nothing on unit testing), see: http://www.edmundkirwan.com/encap/overview/paper7.html

+1  A: 
Steve Freeman
+1  A: 

I have the same uneasy feeling when I write tests like this. It especially strikes me when I implement the function by copy'n'pasting the the expectations into the function body (this works when I use LeMock for mocking).

But its ok. It happens. This test now documents and verifies how the system under test interacts with its dependencies, which is a good thing. There are other problems with this test though:

  1. It is testing too much at once. This test verifies that three dependencies are called correctly. If either of those dependencies change, this test will have to change. It would be better to have 3 separate tests, verifying each dependency is properly handled. Pass in a stub object for the dependencies you aren't testing (rather than a mock, as it will fail).

  2. Theres no verification on the parameters passed to the dependencies, so these tests are incomplete.

In this sample I will use Moq as the mocking library. This test does not specify the behavior of all the dependencies, it only tests one call. It will also check that the passed in parameter is whats expected given the input, variations of input would justify separate tests.

public void AddEventSavesSnapshot(object eventSnaphot)
{
    Mock<IDocumentDao> mockDocumentDao = new Mock<IDocumentDao>();
    Mock<IPatientSnapshotService> mockPatientSnapshot = new Mock<IPatientSnapshotService>();

    string eventSample = Some.String();
    EventSection eventSection = new EventSection(eventSample);

    mockPatientSnapshot.Setup(r => r.SaveEventSnapshot(eventSample));

    AddDocumentEventService sut = new AddDocumentEventService();
    sut.DocumentDao = mockDocumentDao;
    sut.PatientSnapshotService = mockPatientSnapshot;

    sut.AddEvent(eventSection);

    mockPatientSnapshot.Verify();
}

Note that the unused dependencies that are passed in would only be needed in this test if AddEvent() might use them. Quite reasonably the class might have dependencies not related to this test not shown.

Frank Schwieterman
Thanks for the feedback. Can you elaborate on the 2 great points you raise. Maybe with a little pseudo-code if you have the time.
Dirk