views:

45

answers:

3

Say I have the following business logic:

    foreach (var item in repository.GetAll())
    {
        if (SomeConditition)
        {
            item.Status = Status.Completed;
            repository.Update(item);
        }
    }

Now I write the following unit test:

    public void Test()
    {
        var repository = new FakeRepository();
        repository.Add(new Item());

        RunBusinessLogic(repository);

        Assert.AreEqual(Status.Completed, repository[0].Status);
    }

FakeRepository is implemented over a List, and GetAll() simply returns the list.

While I can test most of the logic involved using this approach, I cannot verify that I have remembered to call repository.Update() in the business code. Because FakeRepository.GetAll() returns the actual objects, the repository objects will already have been modified by business code before I call Update(). In fact, FakeRepository.Update() does nothing.

I realize I can use FakeRepository.Update to record that it has been called, an Assert that in the test. But if I forget to call Update, I cannot be trusted to remember to Assert the Update either, right? I would prefer the test to simply fail if the Update call was omitted.

Any ideas?

+3  A: 

A mocking framework might be useful here as it allows you to verify which methods are actually called. To name a few:

And here's a nice comparison between those three popular frameworks.

Darin Dimitrov
As I noted above, I can also use my fake repository to verify whether or not Update has been called. But I would like my test to fail even if I don't do that when I have forgotten to call Update in the business code. If my fake repository was more realistic and returned copies rather than the real repository object references from GetAll, the test would properly fail. Thus, using an in-memory database as repository would be one way to solve my problem. But I'd like to know if there are simpler solutions.
Tor Hovland
+1  A: 

I realize I can use FakeRepository.Update to record that it has been called, an Assert that in the test. But if I forget to call Update, I cannot be trusted to remember to Assert the Update either, right? I would prefer the test to simply fail if the Update call was omitted.

When you notice that you're not testing that Update is called, or you notice that Update isn't called, that's when you write a test that Update is called. Not that the item in the repository is completed, but that Update is called (it's a different test). You've decided that it's important to you that Update be called, so you need to test that. The way to go about it is exactly as you outline - record the call to update in your FakeRepository, and test against that.

Carl Manaster
Yes, I am thinking that this must be the way it has to be. But the problem with this is that I will have to wait until the update-missing-bug surfaces until I know I have to test it and fix it. If my fake repository was a little more realistic, and didn't return real repository object references from GetAll, my test would fail if Update was forgotten.
Tor Hovland
+1  A: 

There are several things that can be unit-tested separately here: the Repository.getAll() and the Repository.update() methods, and the evaluation of the completion condition for each item, or each kind of item. I'm not sure I will use a FakeRepository here.

As suggested, another option would be to have the FakeRepository.update() method recording and/or counting the number of updates.

But if I forget to call Update, I cannot be trusted to remember to Assert the Update either, right?

Correct, but when you follows the TDD process, you will fail your test first - red - then you write the code to make it pass - green. You will write the assertion first, causing the test to fail, and then invoke the update() method, making the test pass.

philippe
Yes, we can test GetAll and Update with integration tests, and the completion condition with unit tests. But these could all be green even if we forget to call Update in my example loop. In other words, I am never told that I need to call Update.
Tor Hovland
GetAll() and Update() can be unit-tested as well, I'll rephrase my answer. A common metaphor for TDD is dual entry bookkeeping, as such, it *reduces* the probability of making an error, you have to make two errors so that it's not detected.
philippe