简体   繁体   English

尝试使用PowerMockito模拟ProcessBuilder的构造函数时出错

[英]Error trying to mock constructor for ProcessBuilder using PowerMockito

I am trying to mock the constructor for ProcessBuilder. 我正在尝试模拟ProcessBuilder的构造函数。 The problem is that when the constructor is called it return null. 问题在于调用构造函数时,它返回null。

Class code: 班级代码:

 public static void enable() throws IOException, InterruptedException {


        logger.info("Enable NTP server...");

        String ntpAddress = AppConfig.getInstance().getString(AppConfig.NTP_SERVER, "");
        AppConfig.getInstance().getBoolean(AppConfig.NTP_ENABLED, true);
        String enableNtp = "chkconfig ntpd on " + SEPARATOR + " service ntpd stop " + SEPARATOR + " ntpdate " + ntpAddress + " " + SEPARATOR + " service ntpd start";

        String[] commandArr = {"bash", "-c", enableNtp};

        ProcessBuilder pb = new ProcessBuilder(commandArr);
        pb.redirectErrorStream(true);
        Process proc = pb.start();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(
                proc.getInputStream()))) {

            String line;
            while ((line = in.readLine()) != null) {
                logger.info(line);
            }
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error while trying to enable NTP Server");
        }

        proc.waitFor();
        proc.destroy();
        logger.info("NTP server has been enabled");


    }

Test code: 测试代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest({NtpServerUtil.class, ProcessBuilder.class})
public class NtpServerUtilTest extends AbstractDbTest {

    @Test
    public void testEnableNtp() throws Exception {
        ProcessBuilder pb = PowerMockito.mock(ProcessBuilder.class);
        PowerMockito.whenNew(ProcessBuilder.class).withAnyArguments().thenReturn(pb);

        NtpServerUtil.enable();
        PowerMockito.verifyNew(ProcessBuilder.class).withArguments(Matchers.anyString());
    }

}

So, when it goes for new ProcessBuilder(command), the result is null. 因此,当使用新的ProcessBuilder(command)时,结果为null。 After that, when processBuilder.start() is called an exception is thrown. 之后,调用processBuilder.start()时会引发异常。 I tried some methods for mocking that constructor. 我尝试了一些模拟该构造函数的方法。 Any ideas, please? 有什么想法吗?

I would try few things in this case: 在这种情况下,我将尝试一些操作:

  1. change PowerMockito.mock to Mockito.mock (it should work normally). 将PowerMockito.mock更改为Mockito.mock(它应该正常工作)。

  2. Try whenNew passing the correct arguments (or, at least, Matchers.anyString()) as parameter. 尝试在whenNew中传递正确的参数(或至少Matchers.anyString())作为参数。

  3. What if you create NtpServerUtil as an instance and spy it? 如果将NtpServerUtil创建为实例并对其进行监视怎么办?

     private ProcessBuilder processBuilder; private NtpServerUtil ntpServerUtil; @Before public void init(){ processBuilder = Mockito.mock(ProcessBuilder.class); ntpServerUtil = Mockito.spy(NtpServerUtil.class); MockitoAnnotations.initMocks(this); PowerMockito.whenNew(ProcessBuilder.class).withArguments(Mockito.anyString()).thenReturn(processBuilder); } @Test public void testEnableNtp() throws Exception { ... NtpServerUtil.enable(); ... } 

Ps.: you forgot to init your mocks there. 附注:您忘记在此处初始化模拟了。 Maybe it is useful also. 也许它也很有用。

In my team it is prohibited to use PowerMock for any newly written code as it is an indicator of poorly written (untestable) code. 在我的团队中,严禁将PowerMock用于任何新编写的代码,因为它表明编写不好(无法使用)的代码。 If you take it as a rule you will normally end up with much cleaner code. 如果将其作为规则,通常会得到清晰得多的代码。

So for your case, your problem is that you are constructing a concrete new instance of ProcessBuilder , but your code does not really care if it operates on a concrete instance of this class or on an interface that defines the contract for all the methods that you need. 因此,对于您的情况,您的问题是您正在构造一个具体的ProcessBuilder新实例,但是您的代码并不真正在乎它是在此类的具体实例上还是在为所有方法定义协定的接口上进行操作需要。 Effectively you only use the start method, so define a corresponding interface first (it is really terrible that Java does not define it already): 实际上,您仅使用start方法,因此首先定义一个相应的接口(Java尚未定义它确实很糟糕):

public interface ProcessStarter {
    Process start() throws IOException;
}

Then add a package visible field or an argument to your method, if you do not like package visible fields for testing purposes, of the sort: Function<String[], ProcessStarter> processStarterProvider and use it in your code: 然后,如果您不喜欢用于测试目的的包可见字段,则将包可见字段参数添加到您的方法中,其类别为: Function<String[], ProcessStarter> processStarterProvider ,并在代码中使用它:

ProcessStarter starter = processStarterProvider.apply(commandArr);
Process proc = starter.start();

Finally, provide the default implementation. 最后,提供默认的实现。 If you go for a field: 如果您去田野:

Function<String[], ProcessStarter> processStarterProvider = (commandArr) -> {
    ProcessBuilder pb = new ProcessBuilder(commandArr);
    pb.redirectErrorStream(true);
    return (ProcessStarter) pb::start;
};

Now you do not need any PowerMock and can do with a trivial mock! 现在您不需要任何PowerMock,并且可以进行简单的模拟!

Having had a second thought about it, even though the above approach with an interface is generally applicable and I would advise to use it throughout, in this particular case you and if you are happy to wrap your IOException into a runtime one, then you can go with a single Function<String[], Process> interface and the following default implementation: 对此进行了重新思考,即使上述带有接口的方法通常适用,我还是建议在整个过程中使用它,在这种特殊情况下,如果您愿意将IOException包装到运行时中,则可以使用单个Function<String[], Process>接口和以下默认实现:

Function<String[], Process> processProvider = (commandArr) -> {
    ProcessBuilder pb = new ProcessBuilder(commandArr);
    pb.redirectErrorStream(true);
    try {
        return pb.start();
    } catch (IOException ex) {
        throw new RuntimeException(ex);
};

It is shorter and as testable as the above one. 它更短,并且可以像以上那样测试。 For clarity I would probably stick to the above longer one though. 为了清楚起见,我可能会坚持使用更长的时间。

In both cases, in test you will need to come up with an instance of Process somehow, which itself also does not implement any interfaces (poor design) and so might need a similar wrapper interface as shown above. 在这两种情况下,在测试中,您都将需要以某种方式提出一个Process实例,该实例本身也没有实现任何接口(不良的设计),因此可能需要一个类似于上述的包装器接口。 But, given an instance of Process your processStarterProvider may look like this in test: 但是,在给定Process实例的情况下,您的processStarterProvider在测试中可能看起来像这样:

Process mockedProcess = ...

myInstance.processStarterProvider = (commandArr) -> () -> mockedProcess;

In the case of Function<String[], Process> it is even simpler: 如果使用Function<String[], Process> ,则更为简单:

Process mockedProcess = ...

myInstance.processProvider = (commandArr) -> mockedProcess;

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

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