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?

You can't avoid calling the superclass constructor. If you don't explicitly call it, the compiler tries to generate a call to the zero-argument default constructor. If it doesn't exist, that's a compile time error.
–
RytmisFeb 22 '11 at 17:52

It would be possible to create a zero-argument default constructor in the original class which just does nothing. Or one could use PowerMocks WhiteBox.newInstance method.
–
mstWealFeb 22 '11 at 18:02

5 Answers
5

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 Document to some other class... with an interface called DocumentSource, 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 Document or even an InputStream directly in its constructor. Of course, at some point you have to have the actual code that loads the InputStream from the resource using the ClassLoader... 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 InputSupplier instance 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.

Unit testing is just one tool in your testing toolbag. But like all tools, it has an intended purpose and a limited scope of applicability. Roy Osherove's Art of Unit Testing would explain that unit tests are a bad fit when external dependencies are involved. This is for reasons that have been stated elsewhere in this question: keeping test runtime speedy, making tests repeatable across developer environments, eliminating false test failures, and so on.

Reading from the file system, even when that's an integral job of a piece of code, is one such external dependency. So it seems to me that you are trying to force this situation to be unit-testable, when really it's not. You should unit test what you can -- using mock libraries and good decoupled design -- and use other testing tools like manual or automated integration tests for testing external dependencies.

You could pass in a custom ClassLoader that provides a testing instance of tocUrl when called. However, why pass in the class loader at all? If all you use is the tocUrl, just pass that in instead of the ClassLoader and stub that. It greatly simplifies things.

Where did you get the idea that a "good" unit test shouldn't access the file system? There's nothing wrong with it, as long as the test is reproducible in multiple environments. So in this case, it means that you create a static file on the test classpath, and you pass that file's path to the ClassLoaderProductDataProvider constructor. No need to make it any more complicated.

Well you can read it almost everywhere if you google "good unit test" or "unit test best pratices". Also, it makes unit tests considerably slower which is bad for TDD. You can run into IOExceptions at any time (e.g. when the tests run on another server without necessary permissions). You can run into problems if you rely that a file isn't there so you can create it but the former test run didn't delete the file ...
–
mstWealFeb 22 '11 at 18:07

4

The file system is an external dependency that, by many definitions of unit test, makes a test that depends on it an integration test of some sort. Avoiding file system access in the unit tests you run all the time during development makes them run faster and eliminates a possible source of test failures that don't actually relate to the class being tested. Of course, if you just mean "automated tests" by unit tests, it's a different story.
–
ColinDFeb 22 '11 at 18:08

@user519499: If the test file is part of the source tree, then there shouldn't be any permission problems (if there are, then you've got bigger problems.) And if you have problems with files that didn't get cleaned up, just clean them up before the test. @ColinD: the "unit" vs "integration" argument is completely pointless, in my opinion. As a developer, I get paid to make sure that the code works - I don't have time to split hairs.
–
Mike BaranczakFeb 22 '11 at 18:18

1

Anyway, it's all beside the point. Reading the file system is what this code does: there's no good way to test it without accessing the file system.
–
Mike BaranczakFeb 22 '11 at 18:19

3

This code shouldn't need to read the file system... it has other responsibilities that should be testable independent from the file system. The file system access can be pushed elsewhere, into some smaller class that really does just read the file system, facilitating testing of this class and possibly others too.
–
ColinDFeb 22 '11 at 18:51

Accessing the file system is perfectly acceptable for unit tests. In fact, it's quite common to have a whole suite of files that you use as fixtures for a system under test. It makes it easy to add new tests because you don't need to add new code, just data.