我有这个类:
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
(老实说,我通常倾向于在类初始化期间实例化记录器,而不是费心测试它们)