使用 JMockit 模拟会在内部 类 的局部变量上抛出 NullPointerException

Mocking with JMockit throws NullPointerException on local variables of internal classes

我正在尝试模拟一个方法,该方法创建一个局部变量,尝试一些事情,并在抛出异常时进行记录。这是一段代码:

public void myFunction() {
    //Some stuff
    try {
        new File("foo").getAbsoluteFile();
    } catch (SecurityException e) {
        //Do some logging
    }
}

我想使用 JMockit(如果重要的话使用 1.8 版)来模拟这种日志记录行为。所以我创建了以下测试:

@Test(expected=SecurityException.class)
public void testAbsoluteFile(
        @Injectable final File file
) throws IOException {

    new Expectations(File.class){{
        new File(anyString);
        result = file;
        file.getAbsoluteFile();
        result = new SecurityException();
    }};

    myFunction();
}

麻烦的是,这似乎让我对 File.getAbsoluteFile() 的内部运作有了一个 NullPointerException,我觉得这绝对很奇怪:

java.lang.Exception: Unexpected exception, expected<java.lang.SecurityException> but was<java.lang.NullPointerException>
Caused by: java.lang.NullPointerException
    at java.io.Win32FileSystem.slashify(Win32FileSystem.java:56)
    at java.io.Win32FileSystem.resolve(Win32FileSystem.java:330)
    at java.io.File.getAbsolutePath(File.java:556)
    at java.io.File.getAbsoluteFile(File.java:572)
    at com.vue.rescoreservice.ExpectationTest.myFunction(ExpectationTest.java:39)
    at com.vue.rescoreservice.ExpectationTest.testAbsoluteFile(ExpectationTest.java:33)

这看起来真的很奇怪,因为它说 Win32FileSystem class 中的局部变量(内部 class 不是典型的 Java API ) 正在抛出 NullPointerException,而它以前从未这样做过。

堆栈跟踪中的行如下:

//In myFunction()
new File("foo").getAbsoluteFile();

//In testAbsoluteFile()
myFunction();

为什么会这样?我怎样才能使 JMockit 不会在内部 classes?

的局部变量上抛出 NullPointerException

该问题已在最新的 jMockit 版本 (1.14) 中修复。如果您现在不想迁移,可以在 1.8 中修复测试(请参见下面的代码)。

在这种情况下,@Injectable 不是必需的。 File 的构造函数针对 Expectations 进行了模拟,这需要模拟 class 本身而不是单个实例。在这种情况下,行为等同于 @Mocked(但 File 将根据 Expectations 块中的调用进行部分模拟)。

myFunction 中的 catch 子句需要重新抛出 SecurityException 才能让测试通过。

@Test(expected=SecurityException.class)
public void testAbsoluteFile() throws IOException {
    new Expectations(File.class) {{
        File file = new File(anyString);

        file.getAbsoluteFile();
        result = new SecurityException();
    }};

    myFunction();
}

如果您更愿意将 mock 声明为参数,它也可以工作,但不会部分模拟 File(将模拟所有方法)。

@Test(expected=SecurityException.class)
public void testAbsoluteFile(@Mocked final File file) throws IOException {

    new Expectations(){{
        new File(anyString);

        file.getAbsoluteFile();
        result = new SecurityException();
    }};

    myFunction();
}

关于@Marc-André 回答的更新。

在我使用 JMockit 1.25 的情况下,我必须在预期之外定义文件变量,让它像这样:

@Test(expected=SecurityException.class)
public void testAbsoluteFile() throws IOException {
    File file = new File("");
    new Expectations(File.class) {{
        file.getAbsoluteFile();
        result = new SecurityException();
    }};

    myFunction();
}

也许是在回答之前对 JMockit 进行了更改。