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

时间:2018-01-19 14:41:27

标签: java unit-testing spring-boot mockito powermockito

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

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测试覆盖中跳过的。我怎样才能为这门课写测试?

2 个答案:

答案 0 :(得分:0)

您可以将模拟引用传递给构造函数:

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

答案 1 :(得分:0)

不得不模仿私人成员应该被视为代码气味并且表明当前设计存在问题。因为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,而不会对实施问题产生任何影响。