How to stub a socket in C?
I've written client code that's supposed to send some data through a socket and read back an answer from the remote server.
I would like to unit-test that code. The function's signature is something along the lines of:
double call_remote(double[] args, int fd);
where fd
is the file descriptor of the socket to the remote server.
Now the call_remote
function will, after sending the data, block on reading the answer from the server. How can I stub such a remote server for unit-testing the code?
Ideally I would like something like:
int main() {
int stub = /* initialize stub */
double expected = 42.0;
assert(expected == call_remote(/* args */, stub);
return 0;
}
double stub_behavior(double[] args) {
return 42.0;
}
I would like stub_behavior
to be called and send the 42.0
value down the stubbed file descriptor.
Any easy way I can do that?
If this is a POSIX system, you can use fork()
and socketpair()
:
#define N_DOUBLES_EXPECTED 10
double stub_behaviour(double []);
int initialize_stub(void)
{
int sock[2];
double data[N_DOUBLES_EXPECTED];
socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
if (fork()) {
/* Parent process */
close(sock[0]);
return sock[1];
}
/* Child process */
close(sock[1]);
/* read N_DOUBLES_EXPECTED in */
read(sock[0], data, sizeof data);
/* execute stub */
data[0] = stub_behaviour(data);
/* write one double back */
write(sock[0], data, sizeof data[0]);
close(sock[0]);
_exit(0);
}
int main()
{
int stub = initialize_stub();
double expected = 42.0;
assert(expected == call_remote(/* args */, stub);
return 0;
}
double stub_behavior(double args[])
{
return 42.0;
}
...of course, you will probably want to add some error checking, and alter the logic that reads the request.
The file descriptor created by socketpair()
is a normal socket, and thus socket calls like send()
and recv()
will work fine on it.
You could use anything which can be accessed with a file descriptor. A file or, if you want simulate blocking behaviour, a pipe.
Note: obviosly socket specific calls (setsockopt, fcntl, ioctl, ...) wouldn't work.
I encountered the same situation and I'll share my approach. I created network dumps of exactly what the client should send, and what the server response should be. I then did a byte-by-byte comparison of the client request to ensure it matched. If the request is valid, I read from the response file and send it back to the client.
I'm happy to provide more details (when I'm at a machine with access to this code)
Here is a C++ implementation (I know, the original question was for C, but it is easy to convert back to C if desired). It probably doesn't work for very large strings, as the socket will probably block if the string can't be buffered. But it works for small unit tests.
/// Class creates a simple socket for testing out functions that write to a socket.
/// Usage:
/// 1. Call GetSocket() to get a file description socket ID
/// 2. write to that socket FD
/// 3. Call ReadAll() read back all the data that was written to that socket.
/// The sockets are all closed by ReadAll(), so this is a one-use object.
///
/// \example
/// MockSocket ms;
/// int socket = ms.GetSocket();
/// send(socket,"foo bar",7);
/// ...
/// std::string s = ms.ReadAll();
/// EXPECT_EQ("foo bar",s);
class MockSocket
{
public:
~MockSocket()
{
}
int GetSocket()
{
socketpair(AF_UNIX, SOCK_STREAM, 0, sockets_);
return sockets_[0];
}
std::string ReadAll()
{
close(sockets_[0]);
std::string s;
char buffer[256];
while (true)
{
int n = read(sockets_[1], buffer, sizeof(buffer));
if (n > 0) s.append(buffer,n);
if (n <= 0) break;
}
close(sockets_[1]);
return s;
}
private:
int sockets_[2];
};
精彩评论