Mockito runnable:想要但没有被调用?

Jon*_*n O 3 android mockito robolectric

在提交错误报告之前,我想确保自己没有做错事.这真的很奇怪.设置:

robolectric 3.0; mockito 1.10.19

被测单位:

public BbScrollView( Context context ){
  this( context, null );
}

public BbScrollView( Context context, AttributeSet attrs ) {
  super( context, attrs );

  mScrollTask = new Runnable() {

    public void run() {
      checkForStopped();
    }
  };
}

public void checkForStopped(){
  int newPosition = getScrollY();
  // the rest is irrelevant , but I hit a breakpoint here.
}

public void startScrollTask() {
  mInitialPosition = getScrollY();
  postDelayed( mScrollTask, mTimeUntilNextCheckForStopped );
}
Run Code Online (Sandbox Code Playgroud)

考试:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class BbScrollViewTests {

  @Test
  public void test_startScrollTask(){
    BbScrollView uut = spy( new BbScrollView( RuntimeEnvironment.application ) );

    // This calls the method above that enqueues the runnable
    uut.startScrollTask();

    // Robolectric runs the runnable
    ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

    // I can hit a breakpoint inside this method but verify() fails
    verify( uut ).checkForStopped();
  }
}
Run Code Online (Sandbox Code Playgroud)

测试失败了:

Wanted but not invoked:
bbScrollView.checkForStopped();
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:62)

However, there were other interactions with this mock:
bbScrollView.startScrollTask();
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:58)

bbScrollView.getScrollY();
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:58)

bbScrollView.$$robo$getData();
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:58)

bbScrollView.postDelayed(
    com.myapp.android.BbKit.view.BbScrollView$1@7f830761,
    100
);
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:58)

bbScrollView.$$robo$getData();
-> at com.myapp.android.BbKit.test.view.BbScrollViewTests.test_startScrollTask(BbScrollViewTests.java:58)
Run Code Online (Sandbox Code Playgroud)

我再说一遍: 我在验证()检查但测试失败的方法中遇到了断点.我也试过在里面创建一个虚拟方法checkForStopped()并验证它无济于事.我还在robolectric UI线程调用的任一侧尝试了1000ms thread.sleep.我的猜测是在robolectric和mockito的反射之间进行交互吗?

Jef*_*ica 6

基于这个Mockito原则,你已经发现了一个非常有趣的预期但不直观的行为:为了创建间谍,Mockito制作原始对象的浅表副本.

当您在构造函数中创建的匿名内部运行的,了Runnable包含一个隐含的参考BbScrollView.this,您的原始 BbScrollView对象.然后,在创建间谍时进行复制,并且对原始BbScrollView的引用仍然存在.这意味着您的调用checkForStopped发生在Mockito无法观察到的原始对象上,而不是间谍.

解决此问题的一种方法是将您的匿名内部Runnable创建移动到您的startScrollTask方法,在间谍上调用,因此this引用间谍.运行Runnable时,它将调用间谍上的方法而不是真实对象,允许Mockito拦截并验证呼叫.