Making Classes Mockable – Virtual Methods vs Interfaces

cmockingrefactoringstubunit testing

In the process of refactoring non-testable code we are re-designing some of ours classes so that it can be easily replaced by a stub or mock during unit tests.

Here is an example of such class (implementation is omitted for simplification):

class LightController {
public:
   void turnOn();
private:
    CanBus m_canBus;
};

The turnOn() method uses internally the CanBus which itself deals with hardware communication (implementation is omitted for simplification):

class CanBus {
public:
    void write(std::array<unsigned char, 8> data);
};

We can't test LightController as is because CanBus won't work without proper hardware environment. The refactoring is planned as follow:

  • Pass The CanBus by dependency injection to the LightController
  • Create a stub class with no hardware dependency to replace CanBus during tests

About the second steps, we can either make functions virtual and create a derived class:

class CanBus {
public:
    virtual void write(std::array<unsigned char, 8> data);
};

class CanBusStub : public CanBus {
public:
    void write(std::array<unsigned char, 8> data) override;
};

Alternatively, we can suggest the creation of a proper interface class and make both CanBus and CanBusStub derive from it:

class CanBusInterface {
public:
    virtual void write(std::array<unsigned char, 8> data) = 0;
};

class CanBus : public CanBusInterface {
public:
    void write(std::array<unsigned char, 8> data) override;
};

class CanBusStub : public CanBusInterface {
public:
    void write(std::array<unsigned char, 8> data) override;
};

I know this is probably an opinion-based question which is extremely dependent on the context, but given our team is fairly new with unit tests and that none of our codebase is based on Interface, would you suggest to go the simpler way (just makes CanBus methods virtual) or the complete way (enforcing creation of interfaces)?

Is there any important downside with making the CanBus methods virtual?

Best Answer

If you have to use virtual functions to stub things out, write your pure virtual interface and make the implementation final, not override. That way you aren't tempted to subclass CanBus and break it's invariants.

Is there any important downside with making the CanBus methods virtual?

They can't be template member functions.

But in C++ you have another option, which doesn't add virtual anywhere.

class CanBus {
public:
    void write(std::array<unsigned char, 8> data); // does real write
};

template <typename Bus>
class BasicLightController {
public:
   void turnOn(); // calls m_CanBus.write as before
private:
    Bus m_canBus;
};

Your integrated code can still have a concrete type

using LightController = BasicLightController<CanBus>;

And test code can define it's own things

class CanBusStub {
public:
    void write(std::array<unsigned char, 8> data); // whatever the test needs
};

optionally, requires C++20

template <typename T> concept CanBus = 
requires(T bus, std::array<unsigned char, 8> data) {
    bus.write(data); 
}

template <CanBus Bus>
class BasicLightController { ... };