views:

82

answers:

1

Please, consider the following (I'm sorry for the amount of code; but this is the minimal example I could think of...):

class SomeDataThingy
{
};

struct IFileSystemProvider
{
    virtual ~IFileSystemProvider() {}
    //OS pure virtual methods
}

struct DirectFileSystemProvider
{
    //Simply redirects the pure virtuals from IFileSystemProvider to OS functions.
}

struct SomeDataBlock
{
    //Stored inside SomeDataThingy; contains specific data
    SomeDataBlock(const SomeDataThingy& subject, const IFileSystemProvider& os = DirectFileSystemProvider())
    {
        //Calculate some data from the Operating System based upon a filename stored in SomeDataThingy.
    }
};

struct IFilter
{
    virtual ~IFilter() {}
    virtual int Matches(const SomeDataThingy&) const = 0;
    virtual void Calculate(SomeDataThingy&) const = 0;
};

class SomeFilter : public IFilter
{
    int Matches(const SomeDataThingy& subject) const
    {
        if (!Subject.Contains<SomeDataBlock>())
            return UNKNOWN;
        else
            return /* This filter matches */
    }
    void Calculate(SomeDataThingy& subject) const
    {
        std::auto_ptr<SomeDataBlock> data(new SomeDataBlock(subject));
        subject.Install<SomeDataBlock>(data);
    }
};

I would like to test SomeFilter::calculate, here. The problem is that the constructor for SomeDataBlock calls out to the filesystem. SomeDataBlock itself is tested by a mock IFileSystemProvider. However, I don't have a simple way to inject the mock into SomeFilter::Calculate; and unfortunately I cannot change the IFilter interface to allow the mock to be passed as an argument to Calculate, because there are other filters for which such a mock would not make any sense.

How can I test Calculate?

+5  A: 

Can you modify the constructor of SomeFilter? If so, you can inject IFileSystemProvider that way.

class SomeFilter : public IFilter
{
public:
    SomeFilter(const IFileSystemProvider& fs = DirectFileSystemProvider())
        : fs(fs)
    {
    }

private:
    int Matches(const SomeDataThingy& subject) const
    {
        if (!Subject.Contains<SomeDataBlock>())
            return UNKNOWN;
        else
            return /* This filter matches */
    }
    void Calculate(SomeDataThingy& subject) const
    {
        std::auto_ptr<SomeDataBlock> data(new SomeDataBlock(subject, fs));
        subject.Install<SomeDataBlock>(data);
    }

    IFileSystemProvider fs;
};

You could also create a public member on SomeFilter to allow the user to provide IFileSystemProvider, before calling Calculate, but after constructing the object.

Merlyn Morgan-Graham
I think modifying the constructor is the way to go.
Pedro d'Aquino
This is one of those solutions that just slaps you in the face, and makes you wonder... .why the hell didn't I think of that? :P
Billy ONeal
@Billy: Don't feel too bad. I had the exact same feeling when I ran into this solution for a very similar (though slightly different) problem. My problem involved the composite design pattern, and how I could customize the object without polluting the abstract interface.
Merlyn Morgan-Graham