I know that a good unit test should never access the file system. So I also know, that you can use Mockito and PowerMock for example to mock out the File class.
But what about the following code:
public ClassLoaderProductDataProvider(ClassLoader classLoader, String tocResourcePath, boolean checkTocModifications) {
// ...
this.cl = classLoader;
tocUrl = cl.getResource(tocResourcePath);
if (tocUrl == null) {
throw new IllegalArgumentException("Can' find table of contents file " + tocResourcePath);
}
this.checkTocModifications = checkTocModifications;
toc = loadToc();
// ...
}
private ReadonlyTableOfContents loadToc() {
InputStream is = null;
Document doc;
try {
is = tocUrl.openStream();
doc = getDocumentBuilder().parse(is);
} catch (Exception e) {
throw new RuntimeException("Error loading table of contents from " + tocUrl.getFile(), e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
try {
Element tocElement = doc.getDocumentElement();
ReadonlyTableOfContents toc = new ReadonlyTableOfContents();
toc.initFromXml(tocElement);
return toc;
} catch (Exception e) {
throw new RuntimeException("Error creating toc from xml.", e);
}
}
This class initializes it’s toc attribute with the contents of the file located at tocResource.
So the first thing that comes to my mind for the test is to create a sub class which does not call super in the constructor so all the file access isn’t done. In my own constructor then I insert test dummy data for the data which should have been read from the file. Then I can test the rest of the class without problem.
However, then the constructor code of the original class is not tested at all. What if there’s an error?
Here’s the thing: typically, to make proper unit testing work, you need to provide your classes with interfaces rather than concrete classes to allow you flexibility in doing different things for testing. Looking at your example, it seems to me that you should extract the responsibility of loading a
Documentto some other class… with an interface calledDocumentSource, say.Then your code here wouldn’t depend on the file system at all. It might look something like
Alternatively, you could have the class take a
Documentor even anInputStreamdirectly in its constructor. Of course, at some point you have to have the actual code that loads theInputStreamfrom the resource using theClassLoader… but you can push that code into something simple that only does that. Then it’s clear that any testing you do of that class must use an actual file… but the testing of other classes is not affected.As a side note, it’s a bad sign for the testability of a class if it does work (such as loading the table of contents in this case) in its constructor. There is probably a much better way of designing the classes involved here that eliminates the need for that and is more testable, but it’s hard to say exactly what that design is given just this.
There are various other options for what you could do as well, including the use of something like Guava’s InputSupplier interface combined with an already-tested factory method like Resources.newInputStreamSupplier(URL) for getting the
InputSupplierinstance for use in production. The key thing, though, is to always have your classes depend on interfaces so that you can make easy use of alternate implementations in testing.