2015-11-11 4 views
3

Я хочу, чтобы я не делал что-то ужасное, прежде чем представить отчет об ошибке. Это действительно странно. Установка:Mockito runnable: хотел, но не был вызван?

robolectric 3.0; Mockito 1.10.19

Unit тестируемой:

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

Тест:

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

Тест терпит неудачу с:

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(
    [email protected], 
    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) 

Повторяю: я ударил точки останова внутри метод, который проверяет() проверку, но тест не выполняется. Я также попытался создать фиктивный метод внутри checkForStopped() и проверить это безрезультатно. Я также пробовал 1000ms thread.sleep по обе стороны от вызова robolectric UI. Я предполагаю, что что-то происходит с взаимодействием между материалами отражения robolectric и mockito?

ответ

5

Вы поймали действительно веселое ожидаемое, но неинтуитивное поведение, основанное на этом принципе Mockito: Чтобы создать шпиона, Mockito makes a shallow copy of the original object.

При создании анонимного внутреннего Runnable в конструкторе Runnable содержит неявную ссылку на BbScrollView.this, вашего оригинального объекта BbScrollView. Затем вы создаете копию при создании шпиона, и ссылка на ваш оригинальный BbScrollView сохраняется. Это означает, что ваш звонок checkForStopped происходит с оригинальным объектом, который Mockito не может наблюдать, а не шпионом.

Один из способов исправить это, чтобы переместить ваше анонимное внутреннее создание Runnable в ваш метод startScrollTask, вызывается на шпиона, поэтому this относится к шпиону. Когда Runnable запущен, он будет вызывать методы для шпиона вместо реального объекта, позволяя Mockito перехватывать и проверять вызов.

+1

Ничего себе. Я бы никогда не нашел это через миллион лет. Престижность к вам, сэр. Ты спас мне много времени. –