模拟对象的方法在接口向下转换后返回null
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟对象的方法在接口向下转换后返回null相关的知识,希望对你有一定的参考价值。
我正在尝试为Spring启动应用程序编写测试。有两个接口INotifier
和IMonospaceNotifier
在应用程序中扩展INotifier
。
public interface INotifier {
void send(String message);
}
public interface IMonospaceNotifier extends INotifier {
String monospace(String message);
}
类TelegramNotifier
实现IMonospaceNotifier
@Component
public class TelegramNotifier implements IMonospaceNotifier {
//Some code omitted
public void send(String message) {
//Implementation omitted
}
@Override
public String monospace(String message) {
return "```
" + message + "
```";
}
}
类Report
具有INotifier
类型的字段,但在某些情况下,它被下载到IMonospaceNotifier
@Component
public class Report {
//Some code is omitted
private INotifier notifier;
@Autowired
public Report(/*params are omitted*/) {
// Some code is omitted
if (reportGenerator.requireMonospace() && !(notifier instanceof IMonospaceNotifier)) {
throw new IllegalArgumentException("If reportGenerator requests monospace method" +
" then notifier should be IMonospaceNotifier");
}
}
@Scheduled(cron = "${reportSchedule}")
public void sendReport() {
// Some code is omitted
String report = reportGenerator.generate(workerList);
if (reportGenerator.requireMonospace()) {
if (notifier instanceof IMonospaceNotifier) {
/**
* This is the problem part. It works fine with normal obejcts
* but method `monospace` returns null with mocked objects.
* I debugged it this codeline is definitely executed and
* `report` is not `null` before the execution of this line
*/
report = ((IMonospaceNotifier) notifier).monospace(report);
} else {
assert true : "Should never happen, checked in constructor";
}
}
notifier.send(report);
}
一切正常,直到IMonospaceNotifier
被嘲笑。随着模拟版本IMonospaceNotifier.monospace()
返回null
(请参阅上面的代码中的评论)。模拟对象似乎具有正确的类型IMonospaceNotifier$$EnhancerByMockitoWithCGLIB$$...
该对象在下一个方面被嘲笑:
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "scheduling.enabled=false")
public class MonitorTest {
@MockBean
private IMonospaceNotifier notifier;
@Test
public void doNothing(){
/** `notifier.send` is invoked in another bean constructor.
* That's why it is working without actual invocation. */
// This works fine as it doesn't use Report class and downcast
verify(notifier).send("Hi!, I'm starting");
// The next invocation is actually null
verify(notifier).send(matches("```┌───.*Worker Name.*"));
verify(notifier).send("I'm shutting down. Good Bye!");
}
}
这就是INotifier
在Monitor
bean的构造函数中调用的方式
@Service
public class Monitor {
@Autowired
public Monitor(/*params are omitted*/ INotifier notifier) {
// This line works fine as it doesn't invoke `monospace`
notifier.send("Hi!, I'm starting");
// In `Report` `send()` is executed with `null` as parameter
// because `monospace()` was invoked
report.sendReport();
}
}
答案
你必须告诉你的模拟返回你想要的东西。在您的情况下,您似乎想要返回作为参数传入的相同对象:
public class MonitorTest {
@MockBean
private IMonospaceNotifier notifier;
@Test
public void doNothing(){
doAnswer((invocation)-> invocation.getArguments()[0]).when(notifier).monospace(anyString());
// ...
但更好的选择是定义一个独立的“报告”,以便您在测试用例中拥有更多控制权:
public class MonitorTest {
@MockBean
private IMonospaceNotifier notifier;
@Test
public void doNothing(){
doReturn(SOME_TEST_REPORT_STRING).when(notifier).monospace(anyString());
// ...
以上是关于模拟对象的方法在接口向下转换后返回null的主要内容,如果未能解决你的问题,请参考以下文章
在构造函数中将类对象强制转换为接口始终返回NULL [duplicate]
片段中的TextView在Android Studio中返回Null