【问题标题】:Mockito NullPointerException on mocked object模拟对象上的 Mockito NullPointerException
【发布时间】:2017-08-21 14:12:28
【问题描述】:

我有这些课程:

public class MyClass(){

   private Logger logger;

   @Override
   public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
     this.collector = outputCollector;
     logger = LoggerFactory.getLogger(MyClass.class);
   }

    @Override
    public void execute(Tuple tuple) {
        if (TupleHelpers.isTickTuple(tuple)) {
            logger.info("Received tick tuple, triggering emit of current window counts");
        emitCurrentWindowAvgs();

        } else {

             ...
        }
    }
}

我想用 Mockito 以这种方式对其进行测试:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

 @Mock
 private Logger logger;

 @InjectMock
 private MyClass myclass;

 @Test
 public void myTest(){
      Tuple tickTuple = MockTupleHelpers.mockTickTuple();
      Myclass myclass = new MyClass();

    // when
    myClass.execute(tickTuple);

    // then
    // verifyZeroInteractions(collector);
    verify(collector).emit(any(Values.class));
}

但是我在 logger.info("...") 上获得 NullPointerException。

我也尝试添加doNothing().when(logger).info(anyString()),但结果没有改变。

我搜索了它,但在大多数情况下,问题在于模拟对象的初始化。

【问题讨论】:

  • MyClass 是做什么的?它与测试无关,对吧?
  • @hunter 我编辑,对不起。

标签: java unit-testing junit nullpointerexception mockito


【解决方案1】:

查看您的代码,我认为您有两个问题,一个是:

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

我假设这会覆盖您的模拟,即使它存在(假设,因为我没有看到何时调用 prepare)。

另一个在你的测试中,你在测试中实例化一个新的MyClass-object,而不是使用由 Mockito 填充的myclass-field。直接使用myclass 就足够了,无需实例化。 Mockito 及其MockitoJunitRunner 将照顾它的人口。

根据您的日志记录框架,您可能还需要考虑专门用于测试的记录器配置。例如,对于 slf4j,至少存在 slf4j-testslf4jtesting,如果必须的话,您甚至可以断言您的日志记录语句。

如果您想保留您的模拟记录器并且不想从外部注入它,您可能需要使用PowerMockito 或类似的东西。这样你就可以通过模拟你的LoggerFactory.getLogger-call 来返回它。提供一种从外部轻松注入记录器的方法可能比使用 PowerMockito 更好。

使用专用于测试的记录器配置的好处显然是,您不需要调整生产代码只是为了测试一切正常,您甚至可以再次节省一些模拟(及其培训);-)

【讨论】:

    【解决方案2】:

    在您的测试中,您定义了一个模拟 Logger。但是你什么也不用做。

    在您的测试类中,您使用LoggerFactory.getLogger(MyClass.class) 创建了一个Logger——事实上您在其他地方创建了一个模拟记录器对此没有影响。在所有情况下,包括测试,这个类都使用真正的记录器。

    解决此问题的一种方法是使Logger 可注入,例如使用构造函数:

     public MyClass(Logger logger) {
          this.logger = logger;
     }
    

    另一个是有一个setLogger(Logger logger) 方法,另一个是允许您的 DI 框架执行字段注入(进入巫毒领域)。

    安排此注入的最简单(最明确)的方法是将其放入@Before 方法中:

     @Mock private Logger mockLogger;
    
     private MyClass myObj; // class under test
    
     @Before setUp() {
          myObj = new MyClass(mockLogger);
     }
    

    ...但您也可以使用@InjectMock。有些人避免使用@InjectMock 的原因之一是它隐藏了您遇到的确切问题。您希望它注入您的模拟,但如果该类既没有合适的构造函数参数也没有设置器,Mockito 将默默地继续而不注入任何东西。

    还有其他测试日志记录的策略,避免使用模拟记录器:How to do a JUnit assert on a message in a logger

    【讨论】:

      【解决方案3】:

      问题出在测试方法上, 你已经在测试类中定义了一个字段

        @InjectMock
        private MyClass myclass
      

      但你没有使用它,在测试方法中你创建了一个新的 myClass 实例

      Myclass myclass = new MyClass();
      

      myClass 字段将被myClass 局部变量隐藏,现在您在测试方法中创建的对象将包含logger 的“null”。没有人分配那个记录器

      【讨论】:

        【解决方案4】:

        首先,您可以让 Mockito 更容易注入该记录器;喜欢:

        public class MyClass(){
        
          public MyClass() {
            this(LoggerFactory.getLogger(IntermediateStatisticsBolt.class));
          }
        
          MyClass(Logger logger) { this.logger = logger; }
        

        这也使得如何将记录器传递给其他测试目的变得更加清晰。

        除此之外,在那个准备方法中:

        logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
        

        是一个静态调用。

        Mockito 无法模拟这些;您将不得不查看 PowerMock(开始测试该方法时)。

        【讨论】:

        • 问题不在于LoggerFactory.getLogger 是一个 static 调用...而是静态调用肯定会覆盖已经注入的模拟。即使没有访问器,Mockito 也可以将模拟注入私有字段。尽管如此,正如其他人所表明的那样,提供一种轻松注入记录器的方法仍然是一个好主意。 ;-)
        【解决方案5】:

        IntermediateStatisticsBolt 没有用你的模拟记录器实例化,所以你会得到一个 NPE,因为它没有在你的类中实例化。您需要注入此记录器(或生产中的真正变体)

        例如

        new IntermediateStatisticsBolt(logger); // i.e. a mock or real logger
        

        (老实说,我通常倾向于在类初始化期间实例化记录器,而不是费心测试它们)

        【讨论】:

        • 有时日志记录是一项功能要求,您确实希望避免回归(例如,您有一些东西可以监控生产日志中的特定模式)——在这种情况下,应该对它们进行单元测试。
        • 确实偶尔会发生,我承认。但在我的经验中相对很少
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-20
        • 1970-01-01
        • 1970-01-01
        • 2013-03-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多