如何使用Mockito模拟Java路径API?
Java路径API是Java文件API的更好替代品,但是大量使用静态方法使得使用Mockito进行模拟变得困难。 从我自己的类中,我注入了一个如何使用Mockito模拟Java路径API?,java,unit-testing,path,mocking,mockito,Java,Unit Testing,Path,Mocking,Mockito,Java路径API是Java文件API的更好替代品,但是大量使用静态方法使得使用Mockito进行模拟变得困难。 从我自己的类中,我注入了一个文件系统实例,在单元测试期间用一个mock替换它 然而,我需要模拟很多方法(也创建了很多模拟)来实现这一点。这在我的测试类中反复发生了很多次。所以我开始考虑设置一个简单的API来注册Path-s并声明相关的行为 例如,我需要检查流打开时的错误处理。 主要类别: class MyClass { private FileSystem fileSyste
文件系统
实例,在单元测试期间用一个mock替换它
然而,我需要模拟很多方法(也创建了很多模拟)来实现这一点。这在我的测试类中反复发生了很多次。所以我开始考虑设置一个简单的API来注册Path-s并声明相关的行为
例如,我需要检查流打开时的错误处理。
主要类别:
class MyClass {
private FileSystem fileSystem;
public MyClass(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
}
测试类:
class MyClassTest {
@Test
public void operation_encounterIOException() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
doThrow(new IOException("fileOperation_checkError")).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
@Test
public void operation_normalBehaviour() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
ByteArrayInputStream in = new ByteArrayInputStream(/* arranged content */);
doReturn(in).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
}
我有很多此类的类/测试,模拟设置可能会更复杂,因为静态方法可能会通过Path API调用3-6个非静态方法。我已经重构了测试以避免大多数冗余代码,但随着Path API使用量的增加,我的简单API往往非常有限。因此,再次是重构的时候了
然而,我所考虑的逻辑看起来很难看,需要很多代码来实现基本用法。我希望简化API模拟的方式(无论是否为Java路径API)基于以下原则:
Answer
,查找实现的方法并返回默认答案。然后在模拟创建时传递此应答的一个实例
有没有直接从Mockito或其他处理问题的方法来实现这一点的现有方法?您的问题是您违反了标准
你有两个顾虑:
查找并定位文件,获取InputStream
处理该文件。
- 事实上,这很可能也被分解成子关注点,但这超出了这个问题的范围
您正试图用一种方法完成这两项工作,这会迫使您做大量额外的工作。相反,将作品分成两个不同的类别。例如,如果您的代码是这样构造的:
class MyClass {
private FileSystem fileSystem;
private final StreamProcessor processor;
public MyClass(FileSystem fileSystem, StreamProcessor processor) {
this.fileSystem = fileSystem;
this.processor = processor;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
processor.process(in);
} catch (IOException e) {
/* business error management */
}
}
}
现在我们把责任分为两部分。从InputStream
执行您想要测试的所有业务逻辑工作的类只需要一个输入流。事实上,我甚至不会嘲笑它,因为它只是数据。您可以按任何方式加载InputStream
,例如使用您在问题中提到的ByteArrayInputStream
您的StreamProcessor
测试中不需要任何Java路径API代码
此外,如果您以常用方式访问文件,则只需进行一次测试即可确保该行为正常工作。您还可以将StreamProcessor
设置为一个接口,然后在代码库的不同部分,为不同类型的文件执行不同的作业,同时将不同的StreamProcessor
传递到文件API中
你在评论中说:
听起来不错,但我不得不忍受大量的遗留代码。我开始介绍单元测试,不想重构太多的“应用程序”代码
最好的办法就是我上面说的。但是,如果您希望进行最小数量的更改以添加测试,则应执行以下操作:
旧代码:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
新代码:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
new StreamProcessor().process(in);
} catch (IOException e) {
/* business error management */
}
}
这是做我上面描述的事情的最不具侵入性的方法。我最初描述的方式更好,但显然这是一种更复杂的重构。这种方式几乎不涉及其他代码更改,但允许您编写测试。听起来不错,但我不得不接受大量遗留代码。我开始介绍单元测试,不想重构太多的“应用程序”代码。更多的操作
方法只是一个模板,除了对文件处理进行抽象之外,我还需要对文件名检索和错误处理进行抽象。最后,“MyClass”变成了一个空壳。。。目前我只有两个“简单”类:一个驱动调用第二个(“MyClass”)的“批处理”。第一个继承自定义批处理基础结构的通用操作和流的全局。我不是SRP极端主义的粉丝:(@mlogan我绝对向你保证,对你来说,在这里进行最基本的重构比使用PowerMockito模拟静态方法更容易。让我编辑一下以进一步解释。
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
new StreamProcessor().process(in);
} catch (IOException e) {
/* business error management */
}
}
public class StreamProcessor {
public void process(InputStream in) throws IOException {
/* file content handling */
/* just cut-paste the other code */
}
}