In C++ I often use RAII-style objects to make code more reliable and allocate them on stack to make code more performant (and to avoid bad_alloc).
But creating an object of concrete class on stack violates the dependency inversion (DI) principle and prevents mocking this object.
Consider the following code:
struct IInputStream
{
virtual vector<BYTE> read(size_t n) = 0;
};
class Connection : public IInputStream
{
public:
Connection(string address);
virtual vector<BYTE> read(size_t n) override;
};
struct IBar
{
virtual void process(IInputStream& stream) = 0;
};
void Some::foo(string address, IBar& bar)
{
onBeforeConnectionCreated();
{
Connection conn(address);
onConnectionCreated();
bar.process(conn);
}
onConnectionClosed();
}
I can test IBar::process
, but I also want to test Some::foo
, without creating real Connection object.
Surely I can use a factory, but it will significantly complicate code and introduce heap-allocation.
Also, I don't like to add the Connection::open
method, I prefer to construct completely initialized and fully functional objects.
I would make Connection
type a template parameter for Some
(or for foo
if extract it as a free function), but I'm not sure that it's right way (templates look like a black magic to many people, so I prefer to use dynamic polymorphism)
What you are doing right now is "force-coupling" the RAII class and the service provider class (which, if you want testability, should really be an interface instead). Address this by:
Connection
into IConnection
ScopedConnection
class that provides RAII on top of thatFor example:
void Some::foo(string address, IBar& bar)
{
onBeforeConnectionCreated();
{
ScopedConnection conn(this->pFactory->getConnection());
onConnectionCreated();
bar.process(conn);
}
onConnectionClosed();
}
By "I can use a factory, but it will significally complicate code and introduce heap-allocation" I meant the following steps:
Create abstract class and derive Connection
from it
struct AConnection : IInputStream
{
virtual ~AConnection() {}
};
Add factory method to Some
class Some
{
.....
protected:
VIRTUAL_UNDER_TEST AConnection* createConnection(string address);
};
Replace stack-allocated connecton by smart pointer
unique_ptr<AConnection> conn(createConnection(address));
To chose between your actual implementation and the mocked one, you have to inject the actual type that you want to construct in some fashion. The way I'd recommend is injecting the type as an optional template parameter. It allows you to unobtrusively use Some::foo
as you used to, but enables you to swap the created connection in case of a test.
template<typename ConnectionT=Connection> // models InputStream
void Some::foo(string address, IBar& bar)
{
onBeforeConnectionCreated();
{
ConnectionT conn(address);
onConnectionCreated();
bar.process(conn);
}
onConnectionClosed();
}
I would not create the overhead of a factory and runtime polymorphism if you know the actual type at compile time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With