简体   繁体   English

模拟私有方法并创建新对象

[英]Mock private method with new object creation

I've written a class which reads the entire file and returns the content. 我写了一个类,它读取整个文件并返回内容。

class ClassToTest {
    public methodToTest(String input) {
       return privateMethod(input);
    }

    private privateMethod(input) {
        ClassPathResource classPathResource = new ClassPathResource(input);
        IOUtils.toString(classPathResource.getFile());
    }
}

Now, inside my test class, I don't want my test to actually read the file from so I'm trying to mock the method classPathResource.getFile() but somehow I'm not able to do so without writing PrepareForTests() and if I do that those test are not counted in JaCoCo. 现在,在我的测试类中,我不希望我的测试实际从中读取文件,因此我试图模拟方法classPathResource.getFile()但不知何故,如果不编写PrepareForTests()和如果我这样做的话,JaCoCo中不计入这些测试。

I've written test case as 我已经写了测试用例

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

The problem is IOUtils.toString gets mocked properly but the call classPathResource.getFile() tries to access the file on the disk. 问题是IOUtils.toString得到了正确的classPathResource.getFile()但是调用classPathResource.getFile()尝试访问磁盘上的文件。 For this, I can do this 为此,我可以这样做

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

And add annotation to my test class as 并添加注释到我的测试类为

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

But now the problem is this test class is skipped from the JACOCO test coverage . 但是现在的问题是,该测试类已从JACOCO测试范围中跳过。 How can I write tests for this class? 如何为此类编写测试?

You can pass a mocked reference into the constructor doing this: 您可以通过以下方式将模拟的引用传递给构造函数:

class ClassToTest {
    private ClassPathResource classPathResource;

    public ClassToTest(ClassPathResource classPathResource) {
        this.classPathResource = classPathResource;
    }

    public methodToTest(String input) {
        IOUtils.toString(classPathResource.getFile(input));
    }
}

Or you can pass the mocked reference into the method doing this: 或者,您可以将模拟的引用传递给执行此操作的方法:

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

Having to mock a private member should be seen as a code smell and an indication that something is wrong with the current design. 必须嘲笑私人成员应被视为一种代码味道,并表明当前设计存在问题。 Because ClassPathResource is being initialized internal to the subject class it is now tightly coupled to that class. 由于ClassPathResource是在主题类内部进行初始化的,因此现在已与该类紧密耦合。 While not entirely impossible to mock it does make testing the class cleanly more difficult. 尽管并不是完全不可能进行模拟,但它确实使测试类变得更加困难。 Consider inverting the creation of the class to a delegate as a dependency. 考虑将类的创建作为依赖项转交给委托。

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

This will allow the injection of the dependency 这将允许注入依赖项

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

and the dependency can be mocked/faked/stubbed when testing. 并且在测试时可以对依赖进行模拟/伪造/存根。

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

in production code the ClassPathResource would be derived from the abstraction 在生产代码中, ClassPathResource将从抽象派生

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

and it would be associated with the abstraction at the composition root. 它将与组成根的抽象关联。

Following the above suggestions would now allow ClassToTest to be tested in isolation without any knock on effects of implementation concerns. 遵循以上建议,现在可以隔离测试ClassToTest ,而不会影响实现问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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