Mocking the Windows API, Part 1

February 4, 2009

Michael C. Feathers, in his incredibly useful book “Working Effectively with Legacy Code,” demonstrates a useful trick to get code under test. Let’s say you encounter a class that you need to change, and it looks something like this:

class Foo {
public:
    int DivideNumbers(int a, int b) {
        if (b == 0) {
            MessageBox(NULL, "You can't divide by zero!", "Error", MB_ICONEXCLAMATION);
            return -1;
        }
        return a/b;
    }
};

OK, this function is clearly ridiculous. Imagine it as a real, 400-line function found in the wild which does a lot of processing and has a MessageBox call about six indentation levels deep. We need to change some of the hairy logic in it, and we can’t break anything else. For some reason, it was written with a lot of obscurely-named variables declared up front and written just such a way that it is difficult to reason about the effects of things. I know, I know … when does that ever happen, right? Just humor me.

So let’s start to cover it with tests.

class Test_Foo : public CxxTest::TestSuite {
public:
    void test_DivideNumbers_returns_integer_division_result() {
        TS_ASSERT_EQUALS(2, Foo().DivideNumbers(15,7));
    }
};

Compile, run tests. It passes. Good. Next test:

    void test_DivideNumbers_returns_negative_one_if_zero_divisor_given() {
        TS_ASSERT_EQUALS(-1, Foo().DivideNumbers(15,0));
    }

OK, we have a problem. These are supposed to be automated tests, right? We need to put this under test to verify that we get the results we think we should get, but we need to do it as carefully as possible to ensure that we don’t screw it up in the process. How? Well, here’s the reveal - we add the following bit of code to Foo:

    virtual int MessageBox(HWND h, LPCSTR m, LPCSTR c, UINT f) const {
        return ::MessageBox(h, m, c, f);
    }

Next we derive a class from Foo for testing:

class TestableFoo : public Foo {
public:
    virtual int MessageBox(HWND, LPCSTR, LPCSTR, UINT) const {
        return 0;
    }
};

And now we can run our test once we replace Foo() with TestableFoo(). This works because C++’s scoping rules will choose local methods before global functions. Of course, the title of this article is “Mocking the Windows API” and not “Faking the Windows API”, so let’s actually mock the Windows API (The Windows API’s mama was sooo fat … oh, nevermind):

class TestableFoo : public Foo {
public:
    bool m_bMessageBoxCalled;
 
    TestableFoo() : m_bMessageBoxCalled(false) {}
 
    virtual int MessageBox(HWND h, LPCSTR m, LPCSTR c, UINT f) const {
        TS_ASSERT(!lstrcmp(m,"You can't divide by zero!"));
        TS_ASSERT(!lstrcmp(c,"Error"));
        TS_ASSERT(f == MB_ICONEXCLAMATION);
        m_bMessageBoxCalled = true;
        return 0;
    }
};
 
class Test_Foo : public CxxTest::TestSuite {
public:
    void test_DivideNumbers_returns_integer_division_result() {
        TS_ASSERT_EQUALS(2, TestableFoo().DivideNumbers(15,7));
    }
 
    void test_DivideNumbers_returns_negative_one_if_zero_divisor_given() {
        TestableFoo f;
        TS_ASSERT_EQUALS(-1, f.DivideNumbers(15,0));
        TS_ASSERT(f.m_bMessageBoxCalled);
    }
};

Stay tuned for part two. What we have so far works and allows us to test things we otherwise couldn’t, but we are about to make things reusable.

Tags: C++ Windows TDD fixme