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 | |











