我正在尝试为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 "```\n" + message + "\n```";
}
}
类
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
bean的构造函数中调用Monitor
的方式@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());
// ...