4

I need to write simple http client. It could be great to have unit tests for my class. But I don't know how to write proper and testable class.

For example, I have a client like this:

class HTTPClient
{
public:
     HTTPCLient(const std::string& host, const std::string& port): session(host, port) {}

     void send()
     {
         session.sendRequest(someRequest);
         Response response = session.receiveResponse();
         // ...
     }

private:
    SomeLibrary::ClientSession session;
};

How to test send method (that I really send what I want)? I can't mock it. I can write that HTTPClient receives SomeLibrary::ClientSession object in constructor (in test I would pass mock) but is it good design? I think that the way of implementing of session etc. should by hidden in my class.

Do You have any idea?

1
  • If you want to badly enough, you can mock. Have your HTTP client use (for example) a Socket class, and by providing a mock Socket class, you can look at what gets sent, send mock replies, etc. Commented Apr 7, 2016 at 1:56

2 Answers 2

4

Yes, it would be a good design to break that direct dependency on the session implementation and instead inject it. (Not only for testing purpose.)

template <typename SessionT>
class HTTPClient
{
private:

  SessionT session_ {};

public:

  HTTPClient(SessionT session) : session_ {std::move(session)}
  {
  }

  // Optional convenience overload
  HTTPClient(const std::string& host, const int port) : session_ {host, port}
  {
  }

  void
  send()
  {
    this->session_.sendRequest(someRequest);
    auto response = this->session_.receiveResponse();
    // …
  }

};

Note the use of auto for the response type. Alternatively, require that a SessionT provides an appropriate nested typedef.

using ResponseT = typename SessionT::response_type;

If you don't want to use templates, you would have to define a virtual interface for your session class and use it through smart pointers. I recommend to stay with the template.

Note that this generalization has purpose beyond unit testing. For example, you might want to try a different library, use HTTPS with different TLS implementations, connect to a local server via UNIX domain sockets, communicate with a server process via a pair of pipes, or even implement client and server in the same process, communicating via a message queue. Not all of these options may sound relevant to you today but it is good to have the flexibility.

By the way, shouldn't your “session” actually be a “connection”?

0

How to test send method (that I really send what I want)? I can't mock it. I can write that HTTPClient receives SomeLibrary::ClientSession object in constructor (in test I would pass mock) but is it good design?

Yes. You should definitely inject the connection/session into the client.

I think that the way of implementing of session etc. should by hidden in my class.

Maybe; If it should, split HttpClient into three:

  • a client implementation class, as a template over the session type:

    template <typename SessionT>
    class HTTPClientImpl { /* injected session, see @5gon12eder's answer */... };
    
  • a session implementation;

  • an extremely thin client putting it all together:

    class HttpClient: private HTTPClientImpl<SessionT> // session type is now
                                                       // an implementation detail,
                                                       // invisible outside of HttpClient
    {
    };
    

    This class would not be tested; Instead, test the implementation class.

Regarding the testablility of your class, you should test it with a real network connection and a mock server (that serves hard-coded content). Here is a sample HTTP server implementation; It should take less than a workday to take the code, integrate it into your build and change it into an active object (that is running a server in the background) that returns predefined responses. such an active object should allow you to write multiple unit tests with various server responses, quickly.

By doing this, you will get lots of errors that are difficult to mock or simulate without network traffic (for example, the non-deterministic breadown of larger messages into data chunks, depending on current network traffic).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.