Understand Java I/O Streams By Writing Your Own

Omar De Los Santos
5 min readSep 17, 2018

--

Previously, I wrote an article outlining what I learned by writing my own mocks without using a mocking library. After writing even more mocks to test my HTTP server client thread, I can now add a greater understanding of Input and Output streams to that list.

Testing my server’s client thread requires mocking three things: a socket, a router, and a middleware chain. The socket links my server to a client, reading the request it receives from the client and writing a response back to it. After parsing the incoming request and creating a Request instance, every Request goes through a middleware chain (which includes logging and authenticating every Request). The router then sends the final, possibly alteredRequest to the proper Handler, which creates and returns aResponse object. Before writing the Request object back to the client, the Request goes through the middleware chain, this time for appending the appropriate Cross-Origin Resource Sharing Sharing (CORS) header and for logging. After formatting the Response object and converting it to bytes, the thread finally uses the socket to write the response to the client.

Mocking the router and middleware is a relatively simple task. For the router, we simply create a MockRouter that extends Router. Its constructor receives an expected Response object, which its one public #getResponse method can return. For the middleware, we similarly create a MockMiddleware class which extends Middleware. As with the MockRouter, we can pass in an expected Response to the constructor, which the #applyMiddleware method can return.

These hard-coded Requests and Responses decouple our client thread test from the function of an actual router or any number of actual Middleware instances. If we want to ensure that multiple middleware classes alter a Response before being written to the client, we can create and link three instances of MockMiddleware, each returning a completely different Response before running assertions against the Response returned by the last in the chain. This prevents use from using an actual implementation of Middleware, which could be broken or — more likely — could change in the future. This client thread unit test would become too fragile if it depended on any number of Middleware instances.

Mocking the socket isn’t quite as easy. For one, it’s a matter of mocking a core Java library. Up until this point, I had only mocked classes that I had written myself. Second, my client thread wraps the InputStream returned by #getInputStream in an InputStreamReader wrapped by a BufferedReader. I felt like I was a few levels removed from the code I wanted to mock.

My code signaled to me that I’d have to run assertions against the InputStream and OutputStream returned by #socket.getInputStream and #socket.getOutputStream respectively. Unfortunately, the #write method in an OutputStream does not return anything. To make matters more complicated, constructing a Socket with a stream as a parameter was deprecated. Although I spent some time trying to avoid it, I realized that mocking a socket would also require mocking InputStream and OutputStream.

MockInputStream

Creating your own implementation of InputStream isn’t completely outlandish. The documentation states that:

Applications that need to define a subclass of InputStream must always provide a method that returns the next byte of input.

That method is the abstract method #read. Its documentation states that:

Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.

In order to read from the stream, we’d need somewhere from which to read. Just as with MockRouter and MockMiddleware, a mock constructor is the perfect place to inject an expected object. In this case, constructing a MockInputStream with an array of bytes gives us precisely what we need. By saving the inputBytes as an instance variable, the #read method can pop and return the first byte in the array. To ensure that we meet the specification above, we can check the array length and return -1 if it’s already empty. The InputStreamReader (wrapped in a BufferedReader) reading from the MockInputStream will take care of stopping when it receives a -1.

All together, the code looks like this:

If you’re familiar with other types of InputStreams, then you might have noticed that we’ve essentially created our own implementation of ByteArrayInputStream.

MockOutputStream

As above, the documentation also suggests that implementing your own OutputStream is fairly standard.

Applications that need to define a subclass of OutputStream must always provide at least a method that writes one byte of output.

That method is the abstract #write(int b) method. As mentioned above, this method is void, so we can’t actually run assertions against it. To get around that, we’ll create and expose a method unique to MockOutputStream: #getWrittenBytes. Because this method will ultimately return something, it will be possible to run assertions against it.

Because two methods (one setting and one getting) will be making use of an object, this class will also make use of an instance variable. In this case, we’ll avoid the primitive array appending and simply instantiate a ByteArrayOutputStream to an instance variable. Now, whenever the socket writes a byte, it can write it directly to the ByteArrayOutputStream instance. Since the method is void, the #write method has no other functionality.

All together, the code looks like this:

MockSocket

With the two requisite mocks out of the way, the MockSocket seems quite simple. It’s two mocked methods will simply return mock instances of InputStream and OutputStream. To run assertions against whatever the socket writes, we create and expose the #getWrittenString method, which gets MockOutputStream‘s written bytes and converts them to a String.

All together, the code looks like this:

Do note that the type for mockInputStream (line 9) must be MockOutputStream and not OutputStream. This is because line 27 calls #getWrittenBytes — a method that exists only in the mock implementation and not any other implementation of OutputStream.

When testing the client thread (or any class that uses an instance of the Socket class), you’ll know be able to control what a test client “sends” the server (as an array of bytes). Similarly, you’ll be able to run assertions against what the server “sends” (conveniently as a String, but you can alter#getWrittenString method in MockSocket to return a ByteArrayOutputStream or a byte array).

--

--