【问题标题】:Mocking and verifying SLF4J with JMockit使用 JMockit 模拟和验证 SLF4J
【发布时间】:2017-04-01 07:52:57
【问题描述】:

我有一个带有 SLF4J 记录器的类,例如:

 public class MyClass {

    private static final Logger log = LoggerFactory.getLogger(MyClass.class);

    public void foo() {
      log.warn("My warn");
    }
  }

我需要用 JMockit 来测试它,比如:

 @Test
 public void shouldLogWarn(@Mocked Logger log) throws Exception {
   new Expectations() {{
     log.warn(anyString);
   }};
   MyClass my = new MyClass();
   my.foo(); 
 }

经过大量搜索,我想通了,我需要以某种方式使用 MockUp。但是具体怎么弄就不清楚了。

顺便说一句,我正在使用 JMockit(1.29) 的最新版本,您不能再 setField(log) 用于最终的静态字段。

【问题讨论】:

    标签: java unit-testing junit slf4j jmockit


    【解决方案1】:

    JMockit 具有适用于这种情况的 @Capturing 注释

    表示所有扩展/实现模拟类型的类也将被模拟的模拟字段或模拟参数。

    未来捕获模拟类型的实例(即,在测试期间稍后创建的实例)将与模拟字段/参数相关联。在记录或验证模拟字段/参数的期望时,这些关联的实例被视为等同于为模拟字段/参数创建的原始模拟实例。

    这意味着如果您使用@Capturing 而不是@Mocked 对其进行注释,则在测试运行期间创建的每个Logger 都将与您注释的那个相关联。所以以下工作:

     @Test
     public void shouldLogWarn(@Capturing final Logger logger) throws Exception {
       // This really ought to be a Verifications block instead
       new Expectations() {{
         logger.warn(anyString);
       }};
       MyClass my = new MyClass();
       my.foo(); 
     }
    

    附带说明,如果您只想验证某个方法是否已被调用,则最好使用Verifications,因为这是它的用途。所以你的代码看起来像这样:

     @Test
     public void shouldLogWarn(@Capturing final Logger logger) throws Exception {
       MyClass my = new MyClass();
       my.foo(); 
       new Verifications() {{
         logger.warn(anyString);
       }};
     }
    

    或者,您可以在 LoggerLoggerFactory 上使用 @Mocked

    在某些情况下,@Capturing 无法按预期工作,因为注释的工作方式错综复杂。幸运的是,您也可以通过在 LoggerLoggerFactory 上使用 @Mocked 来获得相同的效果,如下所示:

     @Test
     public void shouldLogWarn(@Mocked final LoggerFactory loggerFactory, @Mocked final Logger logger) throws Exception {
       MyClass my = new MyClass();
       my.foo(); 
       new Verifications() {{
         logger.warn(anyString);
       }};
     }
    

    注意: JMockit 1.34 到 1.38 has a bug 会阻止它与 slf4j-log4j12 以及可能的 SLF4J 的其他依赖项一起使用。如果遇到此错误,请升级到 1.39 或更高版本。

    【讨论】:

    • 非常感谢!此外,在这种情况下必须添加“@Mocked LoggerFactory used”,因为没有它会在真正的 LoggerFactory 尝试使用模拟记录器时获得 NPE。
    • @mergoth 如果您的代码使用静态方法,则无需模拟 LoggerFactory。所以private static final Logger LOG = LoggerFactory.getLogger(MyClass.class); 实际上,我看到你正在使用它,所以我不确定你为什么需要一个模拟的 LoggerFactory 实例。我在代码中使用完全相同的东西,但不需要模拟 LoggerFactory。
    • 如果 LoggerFactory 未被模拟,getLogger() 方法会尝试获取记录器实例并最终死于 NPE,同时将已模拟的 Logger 实例放入 logback 库深处的非空强制映射。
    • @mergoth 您使用的是什么版本的 SLF4J?在 1.7.21(撰写本文时的最新版本)中,LoggerFactory 是具有私有构造函数的最终类。查看getLogger() 的源代码,它似乎从未实例化LoggerFactory(很明显,它不能实例化,因为它是私有的)。一定有其他事情发生。
    • @mergoth 我用当前的解决方案尝试了 MyClass 的 SSCCE,它在我的机器上正确运行(在将 log 设为 final 之后,因为它在内部类中被引用)。您的真实示例中一定还有其他问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多