How to fill structure with a mocked function using GoogleMock?

Viewed 59

I have a function for example:

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

The man page tell me "The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket, as known to the communications layer." So I would call the function with:

    struct sockaddr_storage clientAddr;
    struct sockaddr* sa{(sockaddr*)&clientAddr};
    clientLen = sizeof(clientAddr);;

    accept_sock = accept(listen_sock, (struct sockaddr*)&clientAddr, &clientLen);

With mocking accept I would like to fill clientAddr with:

    struct sockaddr_in* sa_in{(sockaddr_in*)&clientAddr};
    sa_in->sin_family = AF_INET;
    sa_in->sin_port = htons(50000);
    inet_pton(AF_INET, "192.168.1.2", &sa_in->sin_addr);

To return a mocked pointer to a filled structure I would use:

    EXPECT_CALL(mocked_sys_socket, accept(listen_sock, _, Pointee(clientLen)))
        .WillOnce(DoAll(SetArgPointee<1>(*sa), Return(accept_sock)));

But that isn't what I need here. The clientAddr structure is already given.

What action instead of SetArgPointee<1>(*sa) I have to use to return with filling the provided structure clientAddr?

2 Answers

IIUC, you want that as a result of a call to accept, the second parameter of accept, which is currently sa_int to be set to the value of sa_next.

If that's the case, I can think of two options:

struct sockaddr {
  std::string name;
};

class MockedSysSocket {
 public:
  MOCK_METHOD(int, accept, (int, sockaddr *, socklen_t *addrlen), ());
};

TEST(MockedSysSocketTest, SideEffect1) {
  MockedSysSocket mocked_sys_socket;

  int accept_sock = 1;
  int listen_sock = 2;

  sockaddr sa_in{"initial"};
  sockaddr sa_next{"next"};

  socklen_t clientLen = sizeof(sockaddr);

  EXPECT_CALL(mocked_sys_socket, accept(listen_sock, &sa_in, &clientLen))
      .WillOnce(DoAll(SetArgPointee<1>(sa_next), Return(accept_sock)));

  auto actual = mocked_sys_socket.accept(listen_sock, &sa_in, &clientLen);
  EXPECT_EQ(sa_in.name, sa_next.name);
  EXPECT_EQ(actual, accept_sock);
}

TEST(MockedSysSocketTest, SideEffect2) {
  MockedSysSocket mocked_sys_socket;

  int accept_sock = 1;
  int listen_sock = 2;

  sockaddr sa_in{"initial"};
  sockaddr sa_next{"next"};

  socklen_t clientLen = sizeof(sockaddr);

  EXPECT_CALL(mocked_sys_socket, accept(listen_sock, &sa_in, &clientLen))
      .WillOnce(WithArg<1>(Invoke([&sa_next, accept_sock](sockaddr *in) {
        *in = sa_next;
        return accept_sock;
      })));

  auto actual = mocked_sys_socket.accept(listen_sock, &sa_in, &clientLen);
  EXPECT_EQ(sa_in.name, sa_next.name);
  EXPECT_EQ(actual, accept_sock);
}

Live example: https://godbolt.org/z/3h4ffcnd7

The answer of Ari shows the solution. To have a complete example here is the implementation I use now.

#include <sys/socket.h>
#include <arpa/inet.h>
#include "gmock/gmock.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SetArgPointee;

//
class SysSocketInterface {
  public:
    virtual ~SysSocketInterface() {}
    virtual int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) = 0;
};

// Version to call the real system function.
class RealSysSocket : public SysSocketInterface {
  public:
    virtual ~RealSysSocket() override {}
    int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) override {
        return ::accept(sockfd, addr, addrlen);
    }
};

// Version to call the mocked system function.
class MockSysSocket : public SysSocketInterface {
  public:
    virtual ~MockSysSocket() override {}
    MOCK_METHOD(int, accept, (int, struct sockaddr*, socklen_t*), (override));
};

// Polymorphic class
class SysSocket {
    SysSocketInterface* m_syssocket;

  public:
    SysSocket(SysSocketInterface* a_syssocket) : m_syssocket(a_syssocket) {}

    std::string get_addr(int a_listen_sock) {
        struct sockaddr_storage clientAddr;
        struct sockaddr_in* clientAddr_in{(sockaddr_in*)&clientAddr};
        socklen_t socklen = sizeof(sockaddr_storage);

        int accept_sock = m_syssocket->accept(a_listen_sock,
                                              (sockaddr*)&clientAddr, &socklen);
        if (accept_sock == -1) {
            return "";
        }

        char text_addr[INET_ADDRSTRLEN];
        if (inet_ntop(AF_INET, &clientAddr_in->sin_addr, text_addr,
                      sizeof(text_addr)) == nullptr) {
            return "";
        }

        unsigned short int port = ntohs(clientAddr_in->sin_port);
        return std::string(text_addr) + ":" + std::to_string(port);
    }
};

//
TEST(SysSocketTestSuite, call_real_sys_socket_accept) {
    RealSysSocket real_sys_socket;
    // Inject real sys_socket object
    SysSocket sys_socket(&real_sys_socket);

    int listen_sock = 0;

    // Test Unit
    // Listen_sock is not a valid socket
    EXPECT_EQ(sys_socket.get_addr(listen_sock), "");
}

TEST(SysSocketTestSuite, mock_sys_socket_accept) {
    // Provide a socket address
    struct sockaddr_storage clientAddrMock;
    struct sockaddr* sa{(sockaddr*)&clientAddrMock};
    struct sockaddr_in* sa_in{(sockaddr_in*)&clientAddrMock};
    sa_in->sin_family = AF_INET;
    sa_in->sin_port = htons(50000u);
    inet_pton(AF_INET, "192.168.1.2", &sa_in->sin_addr);
    socklen_t socklen = sizeof(sockaddr_storage);

    // Instantiate mocked sys_socket object
    MockSysSocket mock_sys_socket;
    // Inject mocked sys_socket object
    SysSocket sys_socket(&mock_sys_socket);

    int listen_sock = 3;
    int accept_sock = 4;

    EXPECT_CALL(mock_sys_socket, accept(listen_sock, _, Pointee(socklen)))
        .WillOnce(DoAll(SetArgPointee<1>(*sa), Return(accept_sock)));

    // Test Unit
    EXPECT_EQ(sys_socket.get_addr(listen_sock), "192.168.1.2:50000");
}
//-----------------------------------------------------------------------------

int main(int argc, char** argv) {
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}

With executing the test I get:

[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from MockedSysSocketTest
[ RUN      ] MockedSysSocketTest.call_real_sys_socket_accept
[       OK ] MockedSysSocketTest.call_real_sys_socket_accept (0 ms)
[ RUN      ] MockedSysSocketTest.mock_sys_socket_accept
[       OK ] MockedSysSocketTest.mock_sys_socket_accept (0 ms)
[----------] 2 tests from MockedSysSocketTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 2 tests.

Reference:
Practical example for Dependency Injection and mocking with Googlemock

Related