Mockito runnable: wanted but not invoked?

别说谁变了你拦得住时间么 提交于 2020-01-24 10:03:38

问题


I want to make sure I'm not doing something terribly wrong before I submit a bug report. This is really weird. The setup:

robolectric 3.0; mockito 1.10.19

Unit under test:

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 );
}

The test:

@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();
  }
}

The test fails with:

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)

I repeat: I hit breakpoints inside the method that verify() checks but the test fails. I've also tried creating a dummy method inside of checkForStopped() and verifying on that to no avail. I've also tried 1000ms thread.sleep on either side of the robolectric UI thread call. My guess is something is going on with an interaction between robolectric's and mockito's reflection stuff?


回答1:


You've caught a really fun bit of expected-but-unintuitive behavior, based on this Mockito principle: To create a spy, Mockito makes a shallow copy of the original object.

When you create the anonymous inner Runnable in the constructor, the Runnable contains an implicit reference to BbScrollView.this, your original BbScrollView object. Then, you make a copy as you create the spy, and the reference to your original BbScrollView persists. This means that your call to checkForStopped happens to the original object that Mockito can't observe, not the spy.

One way to fix this is to move your anonymous inner Runnable creation to your startScrollTask method, invoked on the spy, so this refers to the spy. When the Runnable is run, it will call methods on the spy instead of the real object, allowing Mockito to intercept and verify the call.



来源:https://stackoverflow.com/questions/33656249/mockito-runnable-wanted-but-not-invoked

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!