Reputation: 1692
We're writing unit tests for an existing code base. We're using Google Test/Google Mock for the tests, C++11 and Eclipse CDT with the gcc compiler.
One of our classes aggregates a Boost Socket. It used it as an instance, but luckily we can modify the existing code base, and I changed it to a pointer and injected the socket as a dependency. So I went about mocking the calls to the socket, but there's a problem: the Boost functions are non-virtual.
I found documentation showing how to mock non-virtual functions using hi-perf dependency injection. Trying to implement it as shown, though, has been unsuccessful. For example, the doc says to "templatize your code". So for our class that uses the boost socket, I followed their example. I inserted:
template <class boost::asio::ip::udp::socket>
That is supposed to let us insert our mock class instead of the concrete Boost class. I tried it before the class and separately just before the constructor that accepts the socket, in the header and the implementation file. In each place, I just get tons of errors, most of which are along the lines of "no matching function call for " the constructor call.
Clearly, I'm doing something wrong. Does anyone know of a complete example somewhere?
Update: Per request, here is what we currently have:
GoogleMock mocking non-virtual functions
class PIngester : public IPIngester{
public:
// this is the method that takes the socket. It is the constructor, and the socket
// is a default parameter so the existing code will still work. We added the socket
// as a parameter specifically for unit testing. If no socket is provided, the
// constructor creates one. It only will ever create a concrete Boost
// Socket, not a mock one.
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<boost::asio::ip::udp::socket> socket = std::unique_ptr<boost::asio::ip::udp::socket>(nullptr));
...
Update 2
I defined a generic class type for the template, but that breaks the existing code. Here is my current version:
class PIngester : public IPIngester{
public:
template <class Socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
I think it might be barfing on the default parameter, but I can't be sure. The error messages aren't very helpful:
error: no matching function for call to ‘foonamespace::PIngester::PIngester(boost::asio::io_service&, foonamespace::Band&)’
new PIngester(ioService, band));
This error message is from existing code; it doesn't seem to recognize the default parameter.
Update 3
I've abandoned this approach and decided to write a Boost Socket Wrapper instead. The wrapper will hold the actual Socket instance, and its methods will be direct pass-throughs to the actual Socket. The wrapper's functions will be virtual, and my mock object will inherit from the wrapper. Then my mock object will mock the wrapper's virtual functions.
Upvotes: 1
Views: 1955
Reputation: 2978
The issue, as you've noticed, is that the compiler cannot deduce whatever type Socket
is meant to represent here:
class PIngester : public IPIngester{
public:
template <class Socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
If you try to construct an object of this class without specifying the third argument (as in new PIngester(ioService, band)
), what would Socket
be exactly?
Now, there's no way to explicitly specify any template parameter when calling the constructor, so you can't do something like new PIngester<boost::asio::ip::udp::socket>(ioService, band)
if the constructor is templatized.
Here are a couple ways (out of potentially many) to work around this:
You could templatize the PIngester
class itself:
template <class Socket>
class PIngester : public IPIngester{
public:
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Then calling new PIngester<boost::asio::ip::udp::socket>(ioService, band)
(or your mock class in place of boost::asio::ip::udp::socket
) would work.
You could define a default template parameter:
class PIngester : public IPIngester{
public:
template <class Socket = boost::asio::ip::udp::socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Then PIngester(ioService, band)
would always use this default Boost class, and you'd need to explicitly pass in some socket
that represents a unique pointer to your mock class if you want to use this in tests.
Upvotes: 2