IO Testing
Jakob Jenkov |
Components that perform IO can be a little tricky to unit test. In this text I will give a few suggestions to how you can test such classes.
The reason IO components are a little tricky to test is, that they usually either read
from an InputStream
or write to an OutputStream
, or similar
components. In order to test such components you need to be able to control the
data that is read from an InputStream
, and be able to access the
data written to an OutputStream
.
Testing Input Components
First let's look at a component that reads data from an InputStream
.
The component parses the data read, and breaks it up into String
tokens,
whenever it meets a comma (,). It's not a very sophisticated parser, but it's good enough
for this example.
public class MyIOUnit { protected List<String> tokens = new ArrayList<String>(); public void read(InputStream input) throws IOException { StringBuilder builder = new StringBuilder(); int data = input.read(); while(data != -1){ if(((char)data) != ','){ builder.append((char) data); } else { this.tokens.add(builder.toString().trim()); builder.delete(0, builder.length()); } data = input.read(); } } }
Notice how the read()
method takes an InputStream
as parameters.
Let's look at how to write a unit test for this class.
import org.junit.Test; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.IOException; public class MyIOUnitTest { @Test public void testRead() throws IOException { MyIOUnit unit = new MyIOUnit(); byte[] data = "123,456,789,123,456,789".getBytes(); InputStream input = new ByteArrayInputStream(data); unit.read(input); assertEquals("123", unit.tokens.get(0)); assertEquals("456", unit.tokens.get(1)); assertEquals("789", unit.tokens.get(2)); assertEquals("123", unit.tokens.get(3)); assertEquals("456", unit.tokens.get(4)); assertEquals("789", unit.tokens.get(5)); } }
Notice how a byte array is created from a String
, and then inserted into a
ByteArrayInputStream
instance. The ByteArrayInputStream
is then used as input into the MyIOUnitTest.read()
method. This way it
is possible control the input of the unit during unit test.
Testing Output Components
It is also possible to test components that write to an
OutputStream
using a similar approach.
What you need to do is to use an OutputStream
which collects the written data inside it.
The ByteArrayOutputStream
class does this.
Here is a unit (class) that writes to an OutputStream
:
public class MyIOUnit { protected List<String> tokens = new ArrayList<String>(); public void write(OutputStream output) throws IOException { for(int i=0; i<tokens.size(); i++){ if(i>0){ output.write(','); } output.write(tokens.get(i).getBytes()); } } }
Here is the unit test which uses a ByteArrayOutputStream
to collect the data written
to the OutputStream
passed to the MyIOUnit.write()
method:
import org.junit.Test; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.IOException; import java.io.ByteArrayOutputStream; public class MyIOUnitTest { @Test public void testWrite() throws IOException { MyIOUnit unit = new MyIOUnit(); ByteArrayOutputStream output = new ByteArrayOutputStream(); unit.tokens.add("one"); unit.tokens.add("two"); unit.tokens.add("three"); unit.write(output); String string = new String(output.toByteArray()); assertEquals("one,two,three", string); } }
Notice how the output.toByteArray()
method is called, and passed into a String
.
After that, the created String
is compared to the expected string, "one,two,three".
Reader's and Writer's
If your input or output component uses a Reader
or Writer
instead of an
InputStream
or OutputStream
, you can use the classes
CharArrayReader
and CharArrayWriter
instead of ByteArrayInputStream
and ByteArrayOutputStream
.
You may also be able to use the StringReader
and StringWriter
classes in your unit tests.
Tweet | |
Jakob Jenkov |