如何使用JMockit模拟私有静态字段?

14

我有一个类,类似于以下内容:

class ClassA {
    private static File myDir;

    // myDir is created at some stage

    private static String findFile(final String fileName) {
       for (final String actualBackupFileName : myDir.list()) {
           if (actualBackupFileName.startsWith(removeExtensionFrom(backupFile))) {
               return actualBackupFileName;
            }
       }
    }
}

基本上,我想通过模拟File类来测试这个类,以便在对其调用list()时,它返回我在测试类中定义的字符串列表。

我已经有了以下代码,但目前它还没有起作用,我可能犯了一些明显的错误 - 我是JMockit的新手 - 任何帮助都将不胜感激!

@Mocked("list") File myDir;

@Test
  public void testClassA() {
    final String[] files = {"file1-bla.txt"};

    new NonStrictExpectations() {{
      new File(anyString).list(); 
      returns(files);
   }};

   String returnedFileName = Deencapsulation.invoke(ClassA.class, "findFile","file1.txt");

   // assert returnedFileName is equal to "file1-bla.txt"
  }
在运行上面的测试时,我在ClassA中的myDir字段处遇到了NullPointerException - 看起来它没有被正确地模拟?
3个回答

13
您可以使用 Deencapsulation 类中的 setField 方法。以下是示例说明:
Deencapsulation.setField(ClassA, "File", your_desired_value);

3
我发现在 JMockit 1.45Deencapsulation 类不再有 setField() 方法了(尽管版本 1.3.0 有该方法)。 - cbaldan
请不要直接使用解封装(Deencapsulation),这被认为是一种不好的做法。 - Vadiraj S J
2
有没有替代Deencapsulation的方法?我需要在@Tested对象中设置私有静态常量字段。 - Panu Haaramo

11

JMockit(或其他模拟工具)不会模拟字段或变量,它会模拟类型(类、接口等)。在被测试代码中存储这些类型的实例的位置并不重要。

ClassA的示例测试:

@Test
public void testClassA(@Mocked File myDir)
{
    new Expectations() {{ myDir.list(); result = "file1-bla.txt"; }};

    String returnedFileName = new ClassA().publicMethodThatCallsFindFile("file1.txt");

    assertEquals("file1-bla.txt", returnedFileName);
}

上述方法应该有效。请注意,直接测试private方法(或访问private字段)被认为是不良实践,因此我在这里避免了这种做法。另外,最好避免模拟File类。相反,仅测试您的public方法,并使用实际文件而不是仿冒的文件系统。


非常好,非常感谢您的帮助Rogerio,这段代码只需要进行一个小改动就可以使用 - @Mocked File myDir 必须更改为 @Mocked(methods={"list"}) File myDir。我认为这可能是因为 Deencapsualtion.invoke 调用可能在某个时候需要一个真正的文件对象,而模拟所有方法似乎正在干扰某些内容。我是新用户所以无法为您投票 - 否则我会的! - user2586917
1
太好了!模拟File确实可能会导致意外的失败,至少在旧版本的JMockit中是这样。我已经根据您的更改编辑了答案。 - Rogério
@Rogério,上述创建模拟对象(myDir)的方式会导致引用为null,从而导致myDir.list()失败并出现NPE。有什么想法吗? - mystarrocks
@mystarrocks 本地模拟字段不再受支持。测试已更新为使用模拟参数。 - Rogério
啊,我明白了。出于某种原因,我不喜欢在测试方法签名中通过@Mocked列出一堆模拟对象,除非最多只有3个。我更喜欢使用模拟对象的这种方式。 - mystarrocks
1
不要使用实际的文件。这样做会涉及文件系统,引入竞态条件,并使通常在0.01秒内执行的快速执行单元测试转变为需要0.5秒来执行的慢测试。(现在想象一下你有一千个单元测试,你会发现你会得到一个慢反馈循环,这将破坏原本快速执行单元测试的初衷。) - Lance Kind

-1

试试这个:

new Expectations {{
  invoke(File.class, "list", null, null);
  returns(files);
}}

1
谢谢,但文档说invoke是用于调用静态方法的 - File上的list()不是静态的 - 似乎无法工作。 - user2586917

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接