Faking Out The Win32 API With Templates

When writing a unit test, you want to hold the code under test in a vice. You want the code under test to collaborate with objects whose behavior you control so that you can inject controlled conditions into the code under test. This is fairly straightforward when the code under test collaborates with a class that implements an interface. You can simply write a test harness class with the desired behavior. The test harness class is usually called a “fake” object or a “mock” object. It is not the real collaborator, but an object whose purpose is to inject the desired conditions into the code under test to validate its behavior under different circumstances.

However, Win32 code has another sort of collaborator that is more difficult to fake — the global functions in the Win32 API. What happens when your code calls LoadLibrary and the file can’t be found? Furthermore, with a true unit test you don’t even want to do file I/O because that will make the test run slow. You want unit tests to run as fast as possible because you will be running them often as you make changes to the production code.

In the book “Working Effectively with Legacy Code”, Michael Feathers suggests a mechanism for breaking the dependency to a global API with virtual functions. The approach leads to code that looks like this:

class auto_library
{
public:
    auto_library() : _module(0)
    { }
    ~auto_library()
    {
        if (_module)
        {
            FreeLibrary(_module);
        }
    }
    void load(LPCTSTR path)
    { _module = TWS(LoadLibrary(path)); }
    FARPROC get_proc_address(LPCSTR name)
    { return TWS(GetProcAddress(_module, name)); }

protected:
    virtual HMODULE LoadLibrary(LPCTSTR path)
    { return ::LoadLibrary(path); }
    // similar code for FreeLibrary, GetProcAddress

private:
    HMODULE _module;
};

First, let me discuss this odd thing called TWS in the above class. This is a macro that handles the error checking around calling Win32 API routines that are supposed to return a non-zero value on success. The macro expands into a call to an inline function that throws an exception when the return value is zero. This is our error handling mechanism: map “status codes” and “magic return values” into a C++ exception that is thrown and can be caught by the caller of our class. The details of how TWS is implemented aren’t important for this discussion, you just need to know that when LoadLibrary fails, it will result in an exception and not a zero value for the private _module member.

In this class, instead of calling the global API function directly, we call a member function with the same name and signature. So our production code looks like its just calling regular global API functions. However, because the call goes through a virtual member function, we can derive a test harness from our production code and override the virtual member function to supply fake results from the global API and remember the arguments used with the API. The test harness looks like this:

class auto_library_test : public auto_library
{
public:
    auto_library_test() : auto_library(),
        _load_library_called(false),
        _load_library_last_path(),
        _load_library_fake_result(0),
        _load_library_error(0)
    { }
    // "fake" API
    bool load_library_called() const
    { return _load_library_called; }
    const std::basic_string<TCHAR> &load_library_last_path() const
    { return _load_library_last_path; }
    void set_load_library_result(HMODULE value)
    { _load_library_fake_result = value; }
    void set_load_library_error(UINT value)
    { _load_library_error = value; }

protected:
    virtual HMODULE LoadLibrary(LPCTSTR path)
    {
        _load_library_called = true;
        _load_library_last_path = path;
        ::SetLastError(_load_library_error);
        return _load_library_fake_result;
    }
    // similar code for FreeLibrary and GetProcAddress

private:
    bool _load_library_called;
    std::basic_string<TCHAR> _load_library_last_path;
    HMODULE _load_library_fake_result;
};

This test harness overrides the virtual methods on the base class and provides “fake” implementations which allow us to control the results. Using the public methods on the test harness we can control exactly what kind of results are seen by the production code. We can test that when LoadLibrary fails an exception is thrown. We can test that when GetProcAddress fails, an exception is thrown. We can write other tests that cover all the possibilities of our little dynamically loaded library handler.

However, there is a problem when the code is changed slightly. Suppose that auto_library doesn’t have a load method, but instead calls LoadLibrary in its constructor. This means that we can’t have an auto_library object constructed with a zero module handle, so its destructor always calls FreeLibrary.

class auto_library
{
public:
    auto_library(LPCTSTR path) : _module(TWS(LoadLibrary(path)))
    { }
    ~auto_library()
    {
        FreeLibrary(_module);
    }
    FARPROC get_proc_address(LPCSTR name)
    { return TWS(GetProcAddress(_module, name)); }

private:
    HMODULE _module;
};

Now we can’t test this class using virtual functions for the global API. Why not? Because the “fake” method will be in a class that derives from auto_library, but inside a constructor only methods from the class or its base will be called. We haven’t yet constructed the derived test class so calling LoadLibrary, even when its a virtual member function, won’t result in a call to our fake method.

However, we can handle this problem by using a template class instead of a virtual member function. The idea is similar to what we did with virtual member functions. We’ll take our production class auto_library and make it a template class parameterized on its base class. The base class will supply the implementation of the member functions that shadow the global API. In the production code, we’ll use a template argument that delegates directly to the global API functions. In the test code we’ll use a template argument that supplies our fake API. The class under test now looks like this:

template <typename Base = production_api>
class auto_library_t : public Base
{
public:
    auto_library_t(LPCTSTR path) : Base(),
        _module(TWS(LoadLibrary(path)))
    { }
    ~auto_library_t()
    {
        FreeLibrary(_module);
    }
    FARPROC get_proc_address(LPCSTR name)
    { return TWS(GetProcAddress(_module, name)); }

private:
    HMODULE _module;
};
typedef auto_library_t<production_api> auto_library;

The production API template argument is a class that looks like this:

class production_api
{
protected:
    HMODULE LoadLibrary(LPCTSTR path)
    { return ::LoadLibrary(path); }
    BOOL FreeLibrary(HMODULE module)
    { return ::FreeLibrary(module); }
    FARPROC GetProcAddress(HMODULE module, LPCSTR name)
    { return ::GetProcAddress(module, name); }
};

Because the typedef supplies the class production_api template argument to auto_library_t, the clients of auto_library don’t need to be edited as a result of our change. They will need to be recompiled, however. Because everything is available to the compiler at the time of instantiation of auto_library and all the methods of our production code are inline, the code becomes just as efficient as if we had called the global API functions directly. This wasn’t possible with the virtual method technique.

Now lets take a look at our fake test API:

class test_api
{
public:
    test_api() : _load_library_called(false),
        _load_library_last_path(),
        _load_library_fake_result(0),
        _load_library_error(0)
    { }
    // "fake" API
    bool load_library_called() const
    { return _load_library_called; }
    const std::basic_string<TCHAR> &load_library_last_path() const
    { return _load_library_last_path; }
    void set_load_library_result(HMODULE value)
    { _load_library_fake_result = value; }

protected:
    HMODULE LoadLibrary(LPCTSTR path)
    {
        _load_library_called = true;
        _load_library_last_path = path;
        ::SetLastError(_load_library_error);
        return _load_library_fake_result;
    }
    // similar code for FreeLibrary and GetProcAddress

private:
    bool _load_library_called;
    std::basic_string<TCHAR> _load_library_last_path;
    UINT _load_library_error;
    HMODULE _load_library_fake_result;
};

The test code can now call the inquiry methods on the class under test because they are exposed in the public interface of the test API class and the class under test derives publicly from the API class.

We have one more little wrinkle in our scheme. We’ve made it possible to have the constructor of auto_library_t call our fake API, but how do we vary the results of LoadLibrary in our fake? In the code above, we can’t set the fake result until after constructing the test_api object, but we won’t be able to call set_load_library_result between the construction of our object and the construction of auto_library. We could introduce something as a constructor argument, but that would mean exposing it through the constructor of auto_library_t as well and we don’t want to pollute production code with unit test parameters.

We can address this by adding template parameters for the return values and error codes of the API calls to our test_api class. Each test can then instantiate the fake API with the appropriate template arguments to get the desired result from the fake API even when the constructor of the class under test is calling API functions. Here is the final version of the test API class:

template <
    HMODULE load_library_fake_result,
    UINT load_library_error
    // similar parameters for FreeLibrary and GetProcAddress
>
class test_api
{
public:
    test_api() : _load_library_called(false),
        _load_library_last_path(),
        _load_library_fake_result(load_library_fake_result),
        _load_library_error(load_library_error)
    { }
    // "fake" API
    bool load_library_called() const
    { return _load_library_called; }
    const std::basic_string<TCHAR> &load_library_last_path() const
    { return _load_library_last_path; }
    void set_load_library_result(HMODULE value)
    { _load_library_fake_result = value; }

protected:
    HMODULE LoadLibrary(LPCTSTR path)
    {
        _load_library_called = true;
        _load_library_last_path = path;
        ::SetLastError(_load_library_error);
        return _load_library_fake_result;
    }
    // similar code for FreeLibrary and GetProcAddress

private:
    bool _load_library_called;
    std::basic_string<TCHAR> _load_library_last_path;
    UINT _load_library_error;
    HMODULE _load_library_fake_result;
};

Now we can create different scenarios by instantiating test_api with different template arguments. For example, here is some test code using this technique for auto_library_t. In this code, the exception rt::hr_message is the exception that is thrown by TWS when a Win32 API call fails.

try
{
    // pretend not to find the DLL
    typedef test_api<0, ERROR_FILE_NOT_FOUND,
        TRUE, 0,
        FARPROC(0), ERROR_INVALID_FUNCTION> test_api;
    auto_library_t<test_api> dsetup(_T("dsetup.dll"));
}
catch (const rt::hr_message &bang)
{
    _ASSERTE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == bang.result());
}

try
{
    // pretend to find the DLL, but not the function
    typedef test_api<HMODULE(1), 0,
        TRUE, 0,
        FARPROC(0), ERROR_INVALID_FUNCTION> test_api;
    auto_library_t<test_api> dsetup(_T("dsetup.dll"));
    void *ptr = dsetup.get_proc_address("DirectXSetupGetVersion");
}
catch (const rt::hr_message &bang)
{
    _ASSERTE(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) == bang.result());
}

try
{
    // pretend to find the DLL and the function
    typedef test_api<HMODULE(1), 0,
        TRUE, 0,
        FARPROC(1), 0> test_api;
    auto_library_t<test_api> dsetup;
    dsetup.load(_T("dsetup.dll"));
    void *ptr = dsetup.get_proc_address("DirectXSetupGetVersion");
    _ASSERTE(ptr == FARPROC(1));
    _ASSERTE(dsetup.load_library_called());
    _ASSERTE(dsetup.load_library_last_file_name() == _T("dsetup.dll"));
    _ASSERTE(dsetup.get_proc_address_called());
    _ASSERTE(dsetup.get_proc_address_last_module() == HMODULE(1));
    _ASSERTE(dsetup.get_proc_address_last_name() == "DirectXSetupGetVersion");
}
catch (const rt::hr_message &)
{
    _ASSERTE(false);
}

That list of template arguments is getting pretty long for my tastes and this is only when faking out 3 functions in the Win32 API. We could deal with this by using default values for the template arguments or composing a test_api class from other template classes that dealt with single API functions. If your classes only have a single well defined responsibility, you probably aren’t calling many Win32 API functions in that one class. You can also avoid the template arguments to your test API if you don’t call any API functions in your constructor. You can construct an instance of the class under test and use the test API to set the desired behavior before calling any methods on the class under test.

Error handling paths in code are the hardest part to test, even with manual testing. How can you test the code that fails on file I/O? How can you test the code that fails on network I/O? How can you test the code that calls CreateWindow when you can’t create a window? Now you can write unit tests for all of that code and make sure that your error handling is as robust as the “happy path” down your code. Either of these techniques (virtual methods or templates) can be used on your production code. The template based approach has some advantages over the virtual method approach: it can be used to fake out API calls in constructors and incurs no performance overhead in production code.

Happy unit testing!

One Response to “Faking Out The Win32 API With Templates”

  1. Simon Says:

    Just finished reading the articles in your unit testing section. They answered a lot of questions I had in my head. I’m off to test the hell out of my code now, thanks!

    Like


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: