Mocking a server-client connection with Mockito

15,413

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.
Share:
15,413

Related videos on Youtube

karobar
Author by

karobar

Been a eukaryote for the last 30 or so years and I think I might be starting to get the hang of it. Experience: Extrauterine Human from 1991-present 31 weeks as a Fetus 840 hours as an Embryo 120 hours as a Blastocyst 96 hours as a Zygote 20 hours as a Morula When I'm not breathing or moving, you might be able to find me thinking about sexual reproduction, eating, or drinking. Favorite planet: Earth Favorite temperature: 22°C Favorite pressure: Somewhere around 101 kPa

Updated on September 23, 2022

Comments

  • karobar
    karobar over 1 year

    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.

    • Ashley Frieze
      Ashley Frieze over 7 years
      mockServerSocket = mock(ServerSocket.class); try when(mockServerSocket.accept()).thenReturn(mockTestClientSoc‌​ket); . . . mockTestClientSocket = mock(Socket.class);.... you are defining the client socket mock after you tell Mockito to return it. So the reference that's returned is not the one you expect. Reverse these two and try again.