模拟对象的Mockito NullPointerException

时间:2017-03-28 13:16:22

标签: java unit-testing junit nullpointerexception mockito

我有这个课程:

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()),但结果不会改变。

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

5 个答案:

答案 0 :(得分:1)

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

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

另一个是在您的测试中,您在测试中对新的MyClass - 对象进行实例化,而不是使用由Mockito填充的myclass字段。在没有实例化的情况下直接使用myclass就足够了。 Mockito及其MockitoJunitRunner将照顾其人口。

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

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

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

答案 1 :(得分:1)

首先,你可以让Mockito更容易注入该记录器;像:

public class MyClass(){

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

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

这也使得更清楚如何传入记录器以用于其他测试目的。

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

静态电话。

Mockito不能嘲笑那些;您将不得不考虑使用PowerMock(开始测试该方法时)。

答案 2 :(得分:1)

在测试中,您定义了模拟Logger。但是你没有做任何事情。

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

解决此问题的一种方法是使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的一个原因是它隐藏了您遇到的确切问题。你希望它注入你的模拟,但是如果类既没有合适的构造函数参数也没有setter,Mockito将默默地继续而不注入任何东西。

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

答案 3 :(得分:1)

测试方法存在问题, 您已在测试类中定义了一个字段

  @InjectMock
  private MyClass myclass

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

实例
Myclass myclass = new MyClass();

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

答案 4 :(得分:0)

IntermediateStatisticsBolt未使用您的模拟记录器进行实例化,因此您将获得一个NPE,因为它不会在您的类中实例化。您需要注入此记录器(或生产中的真实变体)

e.g。

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

(我通常倾向于在课堂初始化时实例化记录器,并且不用费心去测试它们,说实话)