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();
}
...
}
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 println
ing "Hello,world!"s. However, the reader never seems to even get ready()
.
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.
I could make your unit test pass by modifying 2 things in your code:
mockServerSocket.accept()
properlyUp 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());
}
...
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:
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();
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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With