views:

438

answers:

8

I'm writing a test for a piece of code that has an IOException catch in it that I'm trying to cover. The try/catch looks something like this:

try {
    oos = new ObjectOutputStream(new FileOutputStream(cacheFileName));
} catch (IOException e) {
    LOGGER.error("Bad news!", e);
} finally {

The easiest way seems to make FileOutputStream throw a FileNotFoundException, but perhaps I'm going about this all the wrong way.

Anyone out there have any tips?

+4  A: 

You could set cacheFileName to an invalid name or to one you know doesn't exist.

lacqui
Yes, I suppose the question should really have been "How do I create a file that does not exist on both Linux and Windows?"On windows, I can use 'new File("X:/")', where X: is a drive letter that does not exist. On Linux, this does not work because that is a valid file name.
magneticMonster
A: 

I'm writing a test for a piece of code that has an IOException catch in it that I'm trying to cover.

I'm not entirely sure I understand your goal, but if you want to test if the exception is thrown, you can tell the test you expect it to throw the exception:

@Test(expected=IOException.class)

Your test will then fail if the exception is not thrown, and succeed if it is thrown (like, if the cacheFileName file does not exist).

Fabian Steeg
A: 

A FileNotFoundException would obviously trigger the catch. The javadoc states the cases where it will be thrown.

You should also consider that the ObjectOutputStream constructor can throw an IOException so may want to cover this case in your tests.

Mark
A: 

Two easy ways would be either set cacheFileName to a non-existent file or set the specified file to read-only access.

-John

John T
A: 

As the code is currently written, you could try to mock out the error() call on the LOGGER object and check to see if it gets called when you expect an IOException.

Your desire to test may have uncovered a fundamental problem with the code as it's written. An error is occurring but there's no boolean or flag value (set the filename to a special pattern) that provides other sections of code to determine if writing to the file was successful. If this is contained in a function, maybe you could return a boolean or set an object level variable.

Dan
+4  A: 

From your comment:

Yes, I suppose the question should really have been "How do I create a file that does not exist on both Linux and Windows?" On windows, I can use 'new File("X:/")', where X: is a drive letter that does not exist. On Linux, this does not work because that is a valid file name.

Look at java.io.File.createTempFile. Use it to create the file and then delete it.

Probably pass it something like:

File tempFile;

tempFile = createTempFile(getClass().getName(), 
                          Long.toString(System.currentTimeMillis());
tempFile.delete();

That should give you a unique name in a platform indendent manner that you can safely use without (much) fear of it existing.

TofuBeer
Thanks. I had tried that earlier but it seemed like the FileOutputStream was auto-creating the file. Turns out I had a "helper" method used in the test that auto-created the file.
magneticMonster
File.createTempFile("prefix", "suffix") will always create an unique file (for the JVM execution) no matter how many times it's called with the same arguments.So you don't need to worry about specifying an unique name when invoking that method.
Ravi Wallau
That is what I figured... I just couldn't be 100% sure given the javadoc.
TofuBeer
IF you want to absolutely ensure the file doesn't get created by another test: As a refinement, instead of deleting the temp file, grab an exclusive lock on it using RandomAccessFile (in rw mode), then getChannel().lock(). Be sure to unlock() and delete in a finally block at the end of your test.
Kevin Day
@Kevin Isn't that platform dependent though? On Unix file locking is advisory while on Windows it is mandatory.
TofuBeer
A: 
cacheFileName = "thisFileShouldNeverExistAndIfItDoesYouAreScrewingUpMyTests";

Sure you could take steps and jump thru hoops to programatically make sure that the file name will never, ever, ever exist, or you could use a String that will never exist in 99.99999% of cases.

matt b
+2  A: 

There are two parts to any test: getting it to happen and measuring that you got the correct result.

Fault Injection

The easiest answer is the one that's already been mentioned, which is to set cacheFileName to a file that will never exist. This is likely the most practical answer in this situation.

However, to cause an arbitrary condition such as an IOException, what you really want is Fault Injection. This forces faults in your code without forcing you to instrument your source code. Here are a few methods for doing this:

  • Mock objects You could use a factory method to create an overridden ObjectOutputStream or FileOutputStream. In test code the implementation would throw an IOException when you wanted to, and in production code would not modify the normal behavior.
  • Dependency Injection In order to get your Mock Object in the right place you could use a framework such as Spring or Seam to "inject" the appropriate object into the class that's doing the work. You can see that these frameworks even have a priority for objects that will be injected, so that during unit testing you can override the production objects with test objects.
  • Aspect Oriented Programming Instead of changing the structure of your code at all, you can use AOP to inject the fault in the right place. For instance, using AspectJ you could define a Pointcut where you wanted the exception to be thrown from, and have the Advice throw the desired exception.

There are other answers to fault injection on Java; for instance a product called AProbe pioneered what could be called AOP in C long ago, and they also have a Java product.

Validation

Getting the exception thrown is a good start, but you also have to validate that you got the right result. Assuming that the code sample you have there is correct, you want to validate that you logged that exception. Someone above mentioned using a Mock object for your logger, which is a viable option. You can also use AOP here to catch the call to the logger.

I assume that the logger is log4j; to solve a similar problem, I implemented my own log4j appender which captures log4j output: I specifically capture only ERROR and FATAL, which are likely to be the interesting log messages in such a case. The appender is referenced in log4j.xml and is activated during the test run to capture error log output. This is essentially a mock object, but I didn't have to restructure all my code that got a log4j Logger.

Jared Oberhaus