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

时间:2017-12-30 11:57:46

标签: java spring mockito

我正在尝试为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();
  }
}

1 个答案:

答案 0 :(得分:1)

你必须告诉你的模拟返回你想要的东西。在您的情况下,您似乎希望返回作为参数传递的相同对象:

navigationController?.navigationBar.prefersLargeTitles = true // Navigation bar large titles
navigationItem.title = "Contacts"
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor(displayP3Red: 0/255, green: 150/255, blue: 136/255, alpha: 1.0)

let searchController = UISearchController(searchResultsController: nil) // Search Controller
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController

然而,更好的选择是定义一个独立的“报告”,以便您在测试用例中拥有更多控制权:

public class MonitorTest {
  @MockBean
  private IMonospaceNotifier notifier;

  @Test
  public void doNothing(){
    doAnswer((invocation)-> invocation.getArguments()[0]).when(notifier).monospace(anyString());
  // ...