无法在 junit 中模拟 BufferedWriter class

Unable to mock BufferedWriter class in junit

我在源代码中使用 BufferedWriter 对象

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

我试图在我的测试用例中模拟它如下:

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

但是,BufferedWriter 不会被模拟,它总是进入实际实现。是因为那个不能模拟 BufferedWriter 因为它是一个具体的 class?这是否意味着 java.io class 中的 none 可以被嘲笑?有没有办法模拟它,或者我做错了什么?

只需使用普通的 mockito。由于 BufferedWriter 不是最终版本,因此无需在此处使用 PowerMockito。

使用简单的 mockito,您可以只写:

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

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

当然,如果您的 BufferedWriter 是在您的方法本身内初始化的,并且您没有方法为给定文件 return 它(顺便说一句,你应该使用 Files.newBufferedWriter()Path).

然而,设计一个真正的解决方案需要您展示您正在测试的代码。

我根本不会嘲笑BufferedWritter。在您的测试中创建一个由 ByteArrayOutputStream 支持的编写器,将其传递给您正在测试的 class,然后在完成后检查它。这可能需要重构您的代码以创建用于测试的接缝。这是一种可能的实现方式:

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

然后你的测试看起来像这样:

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

objectUnderTest.writeToWriter(writer);

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

如果您测试的方法需要打开和关闭流,我喜欢使用 Guava 的 CharSink class:

public void writeToSink(CharSink sink) {
  try (Writer writer = sink.openBufferedStream()) {
    writer.append("some string");
  }
}

您可以使用 JMockit 库模拟 Java IO classes(包括它们的构造函数,因此未来的实例也会被模拟),尽管您可能会遇到诸如 NullPointerException 来自 Writer() 构造函数(取决于模拟是如何完成的,以及哪些 IO class 被模拟)。

但是,请注意 Java IO API 包含许多交互 classes 和深层继承层次结构。在您的示例中,FileWriter class 也可能需要被模拟,否则将创建一个实际文件。

此外,应用程序代码中 IO classes 的使用通常只是一个实现细节,可以轻松更改。例如,您可以从 IO 流切换到写入器,从常规 IO 切换到 NIO,或者使用新的 Java 8 实用程序。或者使用第 3 方 IO 库。

最重要的是,尝试模拟 IO classes 是一个非常糟糕的主意。如果(如另一个答案中所建议的那样)您将客户端代码更改为具有 Writers 等 注入 到 SUT 中,情况会更糟。依赖注入不适合这种事情。

而是使用本地文件系统中的真实文件,最好来自测试后可以删除的测试目录,and/or仅在使用时使用固定资源文件读。本地文件快速可靠,可以进行更有用的测试。某些开发人员会这样说 "a test is not a unit test if it touches the file system",但这只是教条式的建议。