Mockito NullPointerException on mocked object



我有这个类:

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("...")上获得了空指针异常。

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

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

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

我认为即使它在那里,这也会覆盖您的模拟(假设,因为我看不到何时调用prepare)。

另一个是在您的测试中,您在测试中引入一个新的MyClass对象,而不是使用 Mockito 填充的myclass-字段。无需实例化即可直接使用myclass。莫西托及其MockitoJunitRunner将照顾其人口。

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

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

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

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

还有其他测试日志记录的策略,这些策略避免使用模拟记录器: 如何在记录器中对消息执行 JUnit 断言

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

@InjectMock
private MyClass myclass

但是你不使用它,在测试方法中,你已经创建了一个myClass的新实例。

Myclass myclass = new MyClass();

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

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

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

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

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

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

是一个静态调用。

Mockito不能模拟这些;你必须研究PowerMock(当开始测试该方法时)。

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

例如

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

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

相关内容

  • 没有找到相关文章

最新更新