3

I am using a BufferedWriter object in my source code

BufferedWriter outputToErrorFile = new BufferedWriter(new FileWriter(file));
outputToErrorFile.append("some string");

I am trying to mock it in my test case as follows:

BufferedWriter mockBufferedWriter = PowerMockito.mock(BufferedWriter.class);
PowerMockito.whenNew(BufferedWriter.class).withAnyArguments().thenReturn(mockBufferedWriter);
PowerMockito.when(mockBufferedWriter.append(Mockito.any(String.class))).thenThrow(new IOException());

However, the BufferedWriter does not get mocked and it always goes into the actual implementation. Is it because that one cannot mock BufferedWriter as it is a concrete class? Does that mean none of the java.io classes can be mocked? Is there a way to mock it, or is there something I am doing wrong?

Abhishek Chauhan
  • 309
  • 5
  • 15
  • `BufferedWriter` is not `final`, so you need not use PowerMockito, "plain old" mockito is enough; try and `doThrow(exception).when(writer).append(anyString())` – fge Jan 13 '15 at 05:54
  • Not as familiar with PowerMock, but how are you running your tests? Do you need to have `@RunWith(PowerMockRunner.class)`? – mkobit Jan 13 '15 at 06:29
  • @MikeKobit Yes I have that. – Abhishek Chauhan Jan 13 '15 at 06:30

3 Answers3

3

You can mock Java IO classes (including their constructors, so future instances also get mocked) with the JMockit library, though you will likely face difficulties such as a NullPointerException from the Writer() constructor (depending on how the mocking was done, and which IO classes were mocked).

However, note that the Java IO API contains many interacting classes and deep inheritance hierarchies. In your example, the FileWriter class would also probably need to be mocked, otherwise an actual file would get created.

Also, usage of IO classes in application code is usually just an implementation detail, which can easily be changed. You could switch from IO streams to writers, from regular IO to NIO, or use the new Java 8 utilities, for example. Or use a 3rd-party IO library.

Bottom line, it's just a terribly bad idea to try and mock IO classes. It's even worse if (as suggested in another answer) you change the client code to have Writers, etc. injected into the SUT. Dependency injection is just not for this kind of thing.

Instead, use real files in the local file system, preferably from a test directory which can be deleted after the test, and/or use fixed resource files when only reading. Local files are fast and reliable, and lead to more useful tests. Certain developers will say that "a test is not a unit test if it touches the file system", but that's just dogmatic advice.

Rogério
  • 16,171
  • 2
  • 50
  • 63
1

Just use plain mockito. Since BufferedWriter is not final, there's no need to use PowerMockito here.

With plain mockito, you can just write:

final BufferedWriter writer = mock(BufferedWriter.class);
final IOException exception = new IOException();

doThrow(exception).when(writer).append(anyString());

Of course, you'll have a problem if your BufferedWriter is initialized within your method itself, and you have no method to return it for a given file (and by the way, you should use Files.newBufferedWriter() and a Path).

Devising a real solution, however, requires that you show the code you are testing.

fge
  • 119,121
  • 33
  • 254
  • 329
  • Can't it mocked by Mockito or PowerMockito? You are right that it is not final and PowerMockito may not be used. However, I guess you may use it for normal methods also. – Abhishek Chauhan Jan 13 '15 at 06:20
0

I wouldn't mock BufferedWritter at all. Create a writer backed by a ByteArrayOutputStream in your test, pass it to the class you are testing, then inspect it when you are done. This may require refactoring your code to create a seam for testing. Here's one possible implementation:

public void writeToWriter(Writer writer) {
  writer.append("some string");
}

Then your test can look like this:

ByteArrayOutputStream stream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(stream, charset);

objectUnderTest.writeToWriter(writer);

String actualResult = writer.toString(charset);
assertEquals("some string", actualResult);

If the method you are testing needs to open and close the stream, I'm fond of using Guava's CharSink class:

public void writeToSink(CharSink sink) {
  try (Writer writer = sink.openBufferedStream()) {
    writer.append("some string");
  }
}
NamshubWriter
  • 23,549
  • 2
  • 41
  • 59
  • Why do you suggest not mocking the BufferedWriter class? – Abhishek Chauhan Jan 14 '15 at 06:27
  • 1
    @AbhishekChauhan two reasons: 1) mocking is best when you need to make assertions about the interaction, but in this case you don't care about the exact interaction (is the code under test making one call to append() or two?), and 2) it can be problematic to mock classes that you don't control (because when setting the expectations you are hard-coding what the behavior is). The one case where I might mock a writer is if I was testing a case where any interaction with the writer throws an exception (though even then, are you sure you know when the exception would be thrown?) – NamshubWriter Jan 15 '15 at 07:04
  • I agree with your point. I am just curious to know whether the BufferedWriter class is allowed to be mocked or not, because I tried many ways to mock it and have failed to do so. Is it not possible to mock it? – Abhishek Chauhan Jan 16 '15 at 07:20
  • @AbhishekChauhan yes, you can mock BufferedWriter. Just follow fge's suggestion, and pass the mock writer into the method/object being tested. – NamshubWriter Jan 18 '15 at 04:03