Java 模拟对象上的Mockito NullPointerException

Java 模拟对象上的Mockito NullPointerException,java,unit-testing,junit,nullpointerexception,mockito,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(

我有以下课程:

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.whenlogger.infoanyString,但结果没有改变

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

IntermediateStatisticsBolt不是用模拟记录器实例化的,因此您将得到一个NPE,因为它不是在类中实例化的。您需要在生产中注入此记录器或真正的变体

e、 g

我通常倾向于在类初始化过程中实例化记录器,而不去测试它们,老实说,IntermediateStatisticsBolt不是用模拟记录器实例化的,因此您将得到一个NPE,因为它不是在您的类中实例化的。您需要在生产中注入此记录器或真正的变体

e、 g


我通常倾向于在类初始化期间实例化记录器,而不去测试它们,老实说,看看您的代码,我认为您有两个问题,一个是:

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
我假设这将覆盖您的mock,即使它存在,因为我不知道何时调用prepare

另一个是在测试中,在测试中插入一个新的MyClass对象,而不是使用Mockito填充的MyClass字段。直接使用myclass而无需实例化就足够了。Mockito及其MockitoJunitRunner将负责其人口

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

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


使用专用于测试的记录器配置的好处很明显,您不需要调整生产代码来测试一切是否正常,甚至可以节省一些模拟和再次培训-

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
我假设这将覆盖您的mock,即使它存在,因为我不知道何时调用prepare

另一个是在测试中,在测试中插入一个新的MyClass对象,而不是使用Mockito填充的MyClass字段。直接使用myclass而无需实例化就足够了。Mockito及其MockitoJunitRunner将负责其人口

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

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


使用专用于测试的记录器配置的好处很明显,您不需要调整生产代码来测试一切是否正常,甚至可以节省一些模拟和再次培训-

首先,您可以让Mockito更容易地注入该记录器;比如:

public class MyClass(){

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

  MyClass(Logger logger) { this.logger = logger; }
这也使我们更清楚地了解了如何将记录器传递到其他测试目的中

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
这是一个静态呼叫


莫基托不能嘲笑那些;在开始测试该方法时,您必须研究PowerMock。

首先,您可以让Mockito更容易地注入该记录器;比如:

public class MyClass(){

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

  MyClass(Logger logger) { this.logger = logger; }
这也使我们更清楚地了解了如何将记录器传递到其他测试目的中

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
这是一个静态呼叫


莫基托不能嘲笑那些;在开始测试该方法时,您必须查看PowerMock。

在测试中,您定义了一个模拟记录器。但是你什么都不做

在测试的类中,您使用LoggerFactory.getLoggerMyClass.class创建了一个记录器-您在其他地方创建了一个模拟记录器的事实对此没有影响。在所有情况下,包括测试,该类都使用真实的记录器

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

 public MyClass(Logger logger) {
      this.logger = logger;
 }
另一个是使用setLoggerLogger方法,还有一个是允许您的DI框架执行进入伏都教领域的场注入

最简单最明确的arran方法 ge此注射是将其放入@Before方法中:

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
。。。但您也可以使用@InjectMock。一些人避免使用@InjectMock的原因之一是它隐藏了您正在经历的确切问题。您希望它注入您的mock,但是如果该类既没有合适的构造函数参数也没有setter,Mockito将在不注入任何内容的情况下默默地继续


还有其他一些用于测试日志的策略,它们避免使用模拟记录器:

在测试中,您定义了模拟记录器。但是你什么都不做

在测试的类中,您使用LoggerFactory.getLoggerMyClass.class创建了一个记录器-您在其他地方创建了一个模拟记录器的事实对此没有影响。在所有情况下,包括测试,该类都使用真实的记录器

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

 public MyClass(Logger logger) {
      this.logger = logger;
 }
另一个是使用setLoggerLogger方法,还有一个是允许您的DI框架执行进入伏都教领域的场注入

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);
。。。但您也可以使用@InjectMock。一些人避免使用@InjectMock的原因之一是它隐藏了您正在经历的确切问题。您希望它注入您的mock,但是如果该类既没有合适的构造函数参数也没有setter,Mockito将在不注入任何内容的情况下默默地继续


还有其他一些用于测试日志的策略,可以避免使用模拟记录器:

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

  @InjectMock
  private MyClass myclass
但是您没有使用它,在测试方法中您已经创建了myClass的一个新实例

Myclass myclass = new MyClass();

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

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

  @InjectMock
  private MyClass myclass
但是您没有使用它,在测试方法中您已经创建了myClass的一个新实例

Myclass myclass = new MyClass();

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

MyClass做什么?它与测试没有任何关联,对吗?@hunter我编辑,对不起。MyClass做什么?它与测试无关,对吗?@hunter我编辑,对不起。有时日志记录是一项功能性要求,您确实希望避免回归,例如,您有一些东西监视生产日志中的特定模式,在这种情况下,它们应该进行单元测试。我同意您的说法,这种情况偶尔会发生。但在我的经验中,日志记录是一项功能性要求,您确实希望避免回归,例如,您有一些东西在监控生产日志的特定模式,在这种情况下,它们应该进行单元测试。我承认,这种情况偶尔会发生。但根据我的经验,这个问题并不是由于LoggerFactory.getLogger是一个静态调用造成的。。。但是静态调用肯定会覆盖已经注入的mock。Mockito还可以将mock注入私有字段,即使没有访问器。尽管如此,正如其他人所展示的,提供一种方便地注入记录器的方法仍然是一个好主意-问题不是因为LoggerFactory.getLogger是静态调用。。。但是静态调用肯定会覆盖已经注入的mock。Mockito还可以将mock注入私有字段,即使没有访问器。尽管如此,正如其他人所展示的,提供一种方便地注入记录器的方法仍然是一个好主意-