我正在尝试为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 "```\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());
  // ...

07-27 18:48