Search code examples
c++unit-testinggoogletest

How to mock a legacy function without resorting to dependency injection?


Given the following hypothetical legacy production code:

struct Foo
{
     Bar bar;
     bool baz()
     {
         return !bar.qux();
     }
};

How do I mock bar.qux() so that it returns true in one test and false in another test, without resorting to dependency injection?

I'm using Google Test framework, but want to avoid adding so many interfaces for dependency injections. Too many interfaces for the purpose of testability tends to make the production code overly complex.


Solution

  • We can probably do something like this:

    #include <iostream>
    #include <vector>
    #include <deque>
    
    // test/MockBarMonitorsAndControls.hpp
    namespace test
    {
        bool const Bar_qux_default_output = true;
        std::deque<bool> Bar_qux_outputs;
        std::vector<int> Bar_qux_arg0_inputs;
    }
    
    // production/Bar.hpp
    struct Bar
    {
        bool qux(int x);
    };
    
    // production/Bar.cpp
    // bool Bar::qux(int x)
    // {
    //    return static_cast<bool>(x & 2);
    // }
    
    // test/Bar.cpp
    bool Bar::qux(int x)
    {
        test::Bar_qux_arg0_inputs.push_back(x);
    
        if (!test::Bar_qux_outputs.empty())
        {
            auto result = test::Bar_qux_outputs.front();
            test::Bar_qux_outputs.pop_front();
            return result;
        }
        else
        {
            return test::Bar_qux_default_output;
        }
    }
    
    // production/Foo.hpp
    struct Foo
    {
        Bar bar;
        int x = 9;
        bool baz()
        {
            return !bar.qux(x);
        }
    
        void henry();
    };
    
    // test/BarTest.cpp
    void TestBar1()
    {
        test::Bar_qux_outputs.clear();
        test::Bar_qux_arg0_inputs.clear();
    
        test::Bar_qux_outputs.push_back(false);
    
        Foo foo;
        bool pass = true;
    
        auto result = foo.baz();
        if (!result)
        {
            std::cout << "TEST1_ERROR: expected true result" << std::endl;
            pass = false;
        }
    
        auto input = test::Bar_qux_arg0_inputs.front();
        if (input != 9)
        {
            std::cout << "TEST1_ERROR: expected arg0 to be 9 but got " << input << std::endl;
            pass = false;
        }
    
        if (pass)
        {
            std::cout << "TEST1 PASS" << std::endl;
        }
        else
        {
            std::cout << "TEST1 FAIL" << std::endl;
        }
    }
    
    // test/BarTest.cpp
    void TestBar2()
    {
        test::Bar_qux_outputs.clear();
        test::Bar_qux_arg0_inputs.clear();
    
        test::Bar_qux_outputs.push_back(true);
    
        Foo foo;
        foo.x = 21;
    
        bool pass = true;
    
        auto result = foo.baz();
        if (result)
        {
            std::cout << "TEST2_ERROR: expected false result" << std::endl;
            pass = false;
        }
    
        auto input = test::Bar_qux_arg0_inputs.front();
        if (input != 21)
        {
            std::cout << "TEST2_ERROR: expected arg0 to be 21 but got " << input << std::endl;
            pass = false;
        }
    
        if (pass)
        {
            std::cout << "TEST2 PASS" << std::endl;
        }
        else
        {
            std::cout << "TEST2 FAIL" << std::endl;
        }
    }
    
    int main()
    {
        TestBar1();
        TestBar2();
        return 0;
    }
    

    Here's the sample output:

    TEST1 PASS
    TEST2 PASS