Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking a server-client connection with Mockito

Introduction

I'm attempting to test a socket connection by sending a string from one thread to another, where server and client sockets are mocked with Mockito v1.9.5.

Here's the test I'm trying to run:

@Test
public void testConnection() {        
    //associate a mock server socket with the TCP Connection
    TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);
    try {
        //begin thread which listens for clients, then sends "Hello, world" to a connected
        //client.
        connection.listen();
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET)
        );

        long startTime = System.nanoTime();
        for (long interval = 0;
                interval < TIMEOUT_TIME;
                interval = System.nanoTime() - startTime) {
            if (reader.ready()) {
                String receivedMessage = reader.readLine();
                assertEquals("Hello, world!", receivedMessage);
                mockTestClientSocket.close();
                connection.closeSocket();
                return;
            }
        }
        mockTestClientSocket.close();
        connection.closeSocket();
        fail("Failed to receive message.");
    } catch (IOException e) {
        fail(e.getMessage());
    }
}

The test runs until TIMEOUT_TIME and then the assertion that fails is the "Failed to receive message." I specify the behavior of the mocked objects here:

@Before
public void setup() {
    mockServerSocket = mock(ServerSocket.class);
    try {
        when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
    } catch (IOException e) {
        fail(e.getMessage());
    }

    mockTestClientSocket = mock(Socket.class);
    try {
        PipedOutputStream oStream = new PipedOutputStream();
        when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);

        PipedInputStream iStream = new PipedInputStream(oStream);
        when(mockTestClientSocket.getInputStream()).thenReturn(iStream);

        when(mockTestClientSocket.isClosed()).thenReturn(false);
    } catch (IOException e) {
        fail(e.getMessage());
    }
}

Part of what I'm trying to test is the following run() within an inner class which is kicked off in the connection.listen():

class InnerListenerClass implements Runnable {
    @Override
    public void run() {
        try {
            clientSocket = socket.accept();
            writer = new OutputStreamWriter(
               clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);
            out = new PrintWriter(writer, true);
            while (!clientSocket.isClosed()) {
                out.println("Hello, world!");
                Thread.sleep(MILLIS_BETWEEN_MESSAGES);
            }
        } catch (InterruptedException | IOException e) {
            LOG.debug(e.getMessage());
        }
    }

    public InnerListenerClass(final ServerSocket socket) {
        this.socket = socket;
    } 
}

And here's a portion of the TcpSocketConnection.java:

class TcpSocketConnection() {
    public TcpSocketConnection(final ServerSocket serverSocket) {
        checkNotNull(serverSocket);
        this.serverSocket = serverSocket;
    }
    ...
    public final void listen() throws IOException {
        listenerThread = new Thread(new InnerListenerClass(serverSocket));
        listenerThread.start();
    }
    ...
}

How it works in my head

I'm going to attempt to step through the process of my test to add some extra context to this question. Starting at the beginning of testConnection():

TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);

This creates a connection with a mocked ServerSocket associated with it. This creates a thread which kicks off with the following line:

clientSocket = socket.accept();

since socket above is a reference to mockServerSocket, Mockito knows to return a reference to a mock Socket called mockTestClientSocket because of this line:

when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);

Next is the line below: NOTE: I believe this is where my understanding and reality diverge, as I believe based on debugging that this thread is hanging on creating this OutputStreamWriter object. I haven't figured out why.

writer = new OutputStreamWriter(clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);

Make a new OutputStreamWriter given an OutputStream. Mockito knows what the output stream of the mocked client socket should look like because of these lines in the setup section:

PipedOutputStream oStream = new PipedOutputStream();                 
when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);

Also, because the setup happens before the test, we know that our InputStream has a reference to this OutputStream because of this line:

PipedInputStream iStream = new PipedInputStream(oStream);

According to the documentation for this constructor, This "Creates a PipedInputStream so that it is connected to the piped output stream (oStream). Data bytes written to (oStream) will then be available as input from this stream."

The while loop in run() begins and causes "Hello, world!" to be sent out the OutputStream (and also received by the InputStream). Next, we wrap up the inputStream nicely:

BufferedReader reader = new BufferedReader(new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET)); 

By the magic of Mockito, the mockTestClientSocket.getInputStream() call actually returns the iStream from earlier, because of the following line:

when(mockTestClientSocket.getInputStream()).thenReturn(iStream);   

So now we have a reader with an input stream, and that input stream is hooked up to an output stream. That output stream is hooked up to a PrintWriter which is printlning "Hello,world!"s. However, the reader never seems to even get ready().

Question

Why is the created listener thread hanging during creation of an OutputStreamWriter, and how can my Hello, World! string get sent properly from mocked socket to mocked client?

Apologies for being a Mockito/java.net.* newb and being a bit thick in general. I think I've included all the relevant portions of code, but if anything's unclear let me know.

like image 562
karobar Avatar asked Nov 04 '16 03:11

karobar


1 Answers

I could make your unit test pass by modifying 2 things in your code:

1. Mock mockServerSocket.accept() properly

Up to now, you mock mockServerSocket.accept() too early because mockTestClientSocket has not yet been set so it will return null, you need to set it first so your code should rather be:

mockServerSocket = mock(ServerSocket.class);
// Set it first
mockTestClientSocket = mock(Socket.class);

try {
    // Then mock it
    when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
} catch (IOException e) {
    fail(e.getMessage());
}
...

2. Synchronize your threads properly

As you launch a dedicated thread to manage your client socket, you need to synchronize your threads in order to make sure that your message will be ready to be read.

To do this you could:

  1. Simplify your code by calling reader.readLine() but it is a blocking approach since your current thread will wait as long as it is needed for the other thread to write the line (the message here).

The code could be:

BufferedReader reader = ...

String receivedMessage = reader.readLine();
assertEquals("Hello, world!", receivedMessage);
mockTestClientSocket.close();
connection.closeSocket();
  1. Set a value for TIMEOUT_TIME big enough even exaggeratedly big to make sure that the other thread will be ready, so for example as it is a value in nanoseconds, you could set it to 30 seconds so to 30_000_000_000L. If you don't set a value big enough, your test could be unstable in slow and/or overloaded and/or shared system such as server used for continuous integration.
like image 146
Nicolas Filotto Avatar answered Sep 20 '22 19:09

Nicolas Filotto