4

I have a class that wraps Asio. It is meant to simulate communication over domain and tcp sockets but I'm at a loss as to automate unit tests. I looked at FakeIt but it only tests virtual methods, GoogleMocks suggests templating my code so I can then pass a MockAsio implementation for unit tests and the real Asio in production.

Are there any other ways to unit test network code? Fake a domain and tcp socket instead of running the whole stack? And if I go with GoogleMock, why use a class that uses GoogleMock and not my own implementation that does whatever I need?

DerKasper
  • 167
  • 2
  • 11
ruipacheco
  • 15,025
  • 19
  • 82
  • 138
  • 1
    well you don't test the small "unit" anymore if you rely on ASIO. Unit test frameworks just get harder as the tool for testing non-unit tests – Hayt Oct 20 '16 at 14:07
  • I'd like to mock the output of asio so I can focus on the unit. – ruipacheco Oct 20 '16 at 14:25
  • I often mock I/O objects or their I/O service to unit test the application protocol (see the official [custom service example](http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/examples/cpp03_examples.html#boost_asio.examples.cpp03_examples.services)). When testing timers, one can also customize the TimeTrait (see [here](http://blog.think-async.com/2007/08/time-travel.html). For testing I/O, I often find far more value in writing a discrete set of [mcve]s, allowing system calls to occur which can surface OS behaviors masked by mocking I/O objects. – Tanner Sansbury Oct 20 '16 at 16:48
  • If I understand this correctly you template the IO service? – ruipacheco Oct 20 '16 at 18:40
  • @ruipacheco Each I/O object has a template parameter that dictates the I/O service (not the `io_service` class) that it will use. For example, [`basic_stream_socket`](http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_stream_socket.html)'s second template parameter is `StreamSocketService`. A handle to the I/O service is available via [`use_service`](http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/use_service.html). This can be helpful to emulate specific I/O behaviors without having direct access to the `asio::socket`. – Tanner Sansbury Oct 20 '16 at 20:20
  • I'm reviewing this as I read code. If I understand you correctly and read the right files, this is all defined in an Asio file, which I can't change. – ruipacheco Oct 24 '16 at 13:53
  • I have never had to change Asio's files. I would change the type of socket I am instantiating based on `#define` guards for testing. – Tanner Sansbury Oct 26 '16 at 17:28
  • An alternative is not to test the I/O layer at all. The I/O «drives» some program logic and you can test the logic «driving» it manually, as-if it was caused by some I/O. This should work if the I/O layer is relatively thin and simple, when it is possible to disentangle the logic from it. – Language Lawyer Feb 09 '23 at 20:02

2 Answers2

6

I have recently ran into the same problem. Since an Asio service's methods (socket's read_some for example) are generally not virtual, a simple dependency injection is out of question. As far as I understand, there are two possible approaches, both of them are discussed in the Google Mock Cook Book:

High performance dependency injection (duck typing)

Discussed here.

This is the option @ruipacheco had already mentioned in his question.

This option requires templatizing your class, but it introduces the least code overhead.

If, for example, your class uses an Asio tcp socket, constructing an instance of it would look something like:

asio::io_context io_context;
MyClass<asio::ip::tcp::socket> my_class(io_context);

Code to an interface

Discussed here.

This is more or less what @NiladriBose had suggested.

This option requires writing an Asio interface and an Asio concrete adapter, for every service type (socket, timer, etc...)! Nevertheless, it's the most generic and robust one (and it does not require templatizing your class, as the previous option did).

If, for example, your class uses an Asio tcp socket, constructing an instance of it would look something like:

asio::io_context io_context;
AsioSocket socket(io_context);
MyClass my_class(socket);

Factory enhancement

If your class uses multiple instances of Asio services (multiple sockets, timers, etc...), it would probably be better to create an abstract Asio services factory. This factory would receive an io_context in its constructor, and export make_socket, make_timer, etc... methods.

Then, constructing an instance of your class would look something like:

AsioFactory asio_factory(io_context);
MyClass my_class(asio_factory);

Finally, in regard to:

And if I go with GoogleMock, why use a class that uses GoogleMock and not my own implementation that does whatever I need?

See this to understand the difference between mock and fake objects. Generally speaking, I think that mock objects require much less effort. Also see this to figure out how to incorporate a Google mock class with a fake class.

DerKasper
  • 167
  • 2
  • 11
ronhe
  • 126
  • 3
  • 6
-1

I am assuming you want to mock out the ASIO wrapper class which is used by your application. If my assumption is correct then say the wrapper has an interface (oversimplified - but most mock frameworks require a pure abstract including gmock)-

    class Iasio
    {
        virtual ~Iasio()
        {

        }
        virtual void send(std::vector<unsigned char> dataToSend) = 0;
        virtual std::vector<unsigned char > rcv() = 0;
    };

Then you have two options- 1) mock using a mocking framework and in your unit test use the mock ( inject the mock into the classes that are using it using contructor or accessor injection). For this for each unit test scenario you would need to setup the mock object to return your expected data.

2) The first option sometimes can be more cumbersome than writing you own test mock , in such circumstances it is perfectly acceptable to write your own test mock giving you more control. I say more control because mock frameworks are general purpose and they can help in most common scenarios but complex scenarios can demand a bespoke test dummy/mock.

NiladriBose
  • 1,849
  • 2
  • 16
  • 31
  • > I am assuming you want to mock out the ASIO wrapper class which is used by your application. I want to mock asio so I can test my wrapper class. – ruipacheco Oct 20 '16 at 18:33
  • got you. first what does the asio library api look like , are we talking boost::asio ? Secondly i think if you are up for a refactor (of the wrapper class) then you should abstract the asio api behind a simplified interface like in the my answer , using something like a facade pattern. Bitting this bullet can save you headaches ! Mocking would be a piece of cake for simple pure abstract interfaces. – NiladriBose Oct 20 '16 at 20:27
  • But that would require me to edit Asio's code to make the methods virtual, which I can't. – ruipacheco Oct 24 '16 at 14:49
  • No what I meant was to not access ASIO code directly , hide it behind a facade or a proxy which has a simpler API and gives you more control. – NiladriBose Nov 03 '16 at 11:07