模拟对象的方法在接口向下转换后返回null

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟对象的方法在接口向下转换后返回null相关的知识,希望对你有一定的参考价值。

我正在尝试为Spring启动应用程序编写测试。有两个接口INotifierIMonospaceNotifier在应用程序中扩展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!");
  }
}

这就是INotifierMonitor 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的主要内容,如果未能解决你的问题,请参考以下文章

安卓。片段 getActivity() 有时返回 null

在构造函数中将类对象强制转换为接口始终返回NULL [duplicate]

Java 如何实现父类转换为子类的效果?

片段中的TextView在Android Studio中返回Null

Android getActivity() 总是在片段内返回 null

如何在 C++ 中正确地向下转换