使用新对象创建模拟私有方法



我写了一个类来读取整个文件并返回内容。

class ClassToTest {
    public methodToTest(String input) {
       return privateMethod(input);
    }
    private privateMethod(input) {
        ClassPathResource classPathResource = new ClassPathResource(input);
        IOUtils.toString(classPathResource.getFile());
    }
}

现在,在我的测试类中,我不希望我的测试实际读取文件,所以我试图模拟该方法classPathResource.getFile()但不知何故,如果不编写PrepareForTests()我就无法做到这一点,如果我这样做,这些测试不计入 JaCoCo。

我已经编写了测试用例

@Test
public void test_methodToTest() {
     mockStatic(IOUtils.class);
     when(IOUtils.toString(any()).thenReturn("DUMMY_STRING");
     methodToTesT("file1.txt");
     ...
}

问题是IOUtils.toString被正确模拟,但调用classPathResource.getFile()尝试访问磁盘上的文件。为此,我可以这样做

PowerMockito.whenNew(ClassPathResource.class)
            .withAnyArguments().thenReturn(mockedClassPath);

并将注释添加到我的测试类中

@PrepareForTest(ClassToTest.class)
class MyTestClass {
... 
}

但现在的问题是这个测试类是从JACOCO测试覆盖率中跳过的。如何为此类编写测试?

你可以将模拟的引用传递给构造函数,这样做:

class ClassToTest {
    private ClassPathResource classPathResource;
    public ClassToTest(ClassPathResource classPathResource) {
        this.classPathResource = classPathResource;
    }
    public methodToTest(String input) {
        IOUtils.toString(classPathResource.getFile(input));
    }
}

或者,您可以将模拟引用传递到执行此操作的方法中:

class ClassToTest {
    public methodToTest(ClassPathResource classPathResource) {
        IOUtils.toString(classPathResource.getFile());
    }
}

不得不嘲笑私人成员应该被视为一种代码气味,并表明当前设计有问题。由于ClassPathResource是在主题类内部初始化的,因此它现在与该类紧密耦合。虽然并非完全不可能模拟它确实使测试类变得更加困难。请考虑将类的创建作为依赖项反转为委托。

public interface PathResource {
    String getFile(String input);
}

这将允许注入依赖项

class ClassToTest {
    private classPathResource;
    public ClassToTest (PathResource resource) {
        this.classPathResource = resource;
    }
    public String methodToTest(String input) {
        return privateMethod(input);
    }
    private String privateMethod(String input) {
        return IOUtils.toString(classPathResource.getFile(input));
    }
}

并且在测试时可以模拟/伪造/存根依赖项。

public void Test() {
    //Arrange
    //mock creation     
    PathResource resource = mock(PathResource.class); 
    String input = "path";
    String expected = "expected_output";
    //stubbing
    when(resource.getFile(input)).thenReturn(expected);
    ClassToTest subject = new ClassToTest(resource);
    //Act
    String actual = subject.methodToTest(input);
    //Assert
    verify(resource).getFile(input);
    assertEquals(expected, actual);
}

在生产代码中,ClassPathResource将从抽象派生

public class ClassPathResource implements PathResource {
    //...code removed for brevity
}

它将与组合根的抽象相关联。

遵循上述建议现在就可以单独测试ClassToTest,而不会对实施问题产生任何影响。

最新更新