简体   繁体   中英

Unable to mock BufferedWriter class in junit

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?

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 Writer s, 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.

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.

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");
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM