Search code examples
c++socketsc++11googletestgooglemock

How to unit test BSD sockets


I am writing a server/client based C++ application in Ubuntu with BSD socket. I am using Google C++ Test Framework as my unit test framework.

I wonder is there a way that I can create a server and client in my unit test, so I can test listen/accept for the server, and send/receive for both sides.

The question is, if I am going to test socket accept (after listening to a port) for the server, how can I have some client connecting to it in this test? Can I use multi-threading to have a client connect to the server being tested in the same TEST() (or TEST_F()) scope?

I can write a client and manually connect to the server under test for sure, but it defeat the purpose of automated unit test.

I read something about the Google Mock, it looks more like a sanity check for me (seeing which functions are being called, how many times, what got returned, etc.).

Please help, thanks.


Solution

  • Ok, lets start with creating abstraction for network socket. We need this to be able to mock the calls to system functions. Start with something like this:

    class Socket {
    public:
        virtual bool connect(const struct sockaddr *address, socklen_t address_len) = 0;
        virtual Socket* accept(struct sockaddr *restrict address, socklen_t *restrict address_len) = 0;
        /* more functions */
    }
    

    The above code doesn't abstract a lot of stuff and its bind to unix classes, because it is using sockaddr and socklen_t. You should also create abstraction for those two types to have platform independent code, but this depends on your design.

    Not you need to create a concrete TCP/UDP class for using it in real application.

    class TCPSocket : public Socket { 
    public:
        TCPSocket() {
            socket_ = socket(PF_INET, SOCK_STREAM, 0);
            if (socket_ == -1) {
                /* handle errors */
            } 
        }
        TCPSocket(int sock) : socket_(sock) {}
        bool connect(const struct sockaddr *address, socklen_t address_len) override {
            return connect(socket_, address, address_len) == 0;
        }
        Socket* accept(struct sockaddr *restrict address, socklen_t *restrict address_len) override {
            int s = accept(socket_, address, address_len);
            if (s == -1) {
                /* handle errors */
            }
            return new TCPSocket(s);
        }
    private:
        int socket_;
    } 
    

    Phew :) let's move on to your class.

    Lets assume your class is named A and has method for testing method. Your class should either take Socket* in constructor, or method should take Socket* as a parameter. Then in your testing code you can specify a mock.

    class MockSock : public Socket {
    public:        
        MOCK_METHOD2(connect, bool(const struct sockaddr*, socklen_t));
        MOCK_METHOD2(accept, Socket*(struct sockaddr*, socklen_t*));
    }
    

    then just instantiate MockSock and pass it to A or method with proper EXPECT_CALL values.