如何验证JUnit中模拟方法的顺序

时间:2017-08-18 01:34:18

标签: android junit mockito

我有这个结构的类,我需要测试OnRequestListOfLunchsFinished接口

的行为
@Override
public void getListOfLunchs(final OnRequestListOfLunchsFinished callback) {

    zip().onErrorResumeNext(new Function<Throwable, ObservableSource<? extends LunchServiceResponse>>() {

        @Override
        public ObservableSource<? extends LunchServiceResponse> apply(@NonNull Throwable throwable) throws Exception {
            callback.onError(new RuntimeException(throwable));
            callback.onEnd();

            return Observable.empty();
        }

    }).subscribe(new Consumer<LunchServiceResponse>() {

        @Override
        public void accept(LunchServiceResponse response) throws Exception {
            List<Lunch> result = new ArrayList<>();

            List<IngredientResponseVO> ingredients = response.getIngredients();
            Map<Integer, Ingredient> hash = new HashMap<Integer, Ingredient>();

            for (IngredientResponseVO vo : ingredients)
                hash.put(vo.id, new Ingredient(vo.id, vo.name, new BigDecimal(vo.price.toString()), vo.image));

            for(InfoLunchResponseVO vo: response.getLunch()){
                Lunch lunch = new Lunch();
                lunch.setId(vo.id);
                lunch.setImage(vo.image);
                lunch.setName(vo.name);

                for(Integer id : vo.ingredients){
                    Ingredient ingredient = hash.get(id);
                    lunch.addIngredient(ingredient);
                }

                result.add(lunch);
            }

            callback.onSuccess(result);
            callback.onEnd();
        }

    });

    callback.onStart();
}

private Observable<LunchServiceResponse> zip(){
    return Observable.zip(getRequestOfListOfLunchs(), getRequestOfListOfIngredients(), new BiFunction<List<InfoLunchResponseVO>, List<IngredientResponseVO>, LunchServiceResponse>() {

        @Override
        public LunchServiceResponse apply(@NonNull List<InfoLunchResponseVO> infoLunchResponseVOs, @NonNull List<IngredientResponseVO> ingredientResponseVOs) throws Exception {
            return new LunchServiceResponse(infoLunchResponseVOs, ingredientResponseVOs);
        }

    });
}

我有这种测试方法

@Test
public void teste(){
    List<IngredientResponseVO> ingredients = Collections.emptyList();
    List<InfoLunchResponseVO> lunchs = Collections.emptyList();

    when(mockApi.getListOfIngredients()).thenReturn(Observable.just(ingredients));
    when(mockApi.getLunchs()).thenReturn(Observable.just(lunchs));

    mockImplementation.getListOfLunchs(callback);

    InOrder order = inOrder(callback);

    order.verify(callback).onStart();
    order.verify(callback).onSuccess(anyList());
    order.verify(callback).onEnd();

    order.verifyNoMoreInteractions();
}

但我收到了例外:

org.mockito.exceptions.verification.VerificationInOrderFailure: 
Verification in order failure
Wanted but not invoked:
callback.onSuccess(<any>);

如果我这样做:

callback.onStart();
callback.onSuccess(Collections.<Lunch>emptyList());
callback.onEnd();

InOrder order = inOrder(callback);

order.verify(callback).onStart();
order.verify(callback).onSuccess(anyList());
order.verify(callback).onEnd();

order.verifyNoMoreInteractions();

这有效。

如何仅验证我的模拟callback的调用?

2 个答案:

答案 0 :(得分:0)

您必须使用InOrder对象。

mockImplementation.getListOfLunchs(callback);

Mockito.verify(callback).onStart();
Mockito.verify(callback).onSuccess(anyList());
Mockito.verify(callback).onEnd();

Mockito.verifyNoMoreInteractions();

答案 1 :(得分:-1)

AFAICS问题不在于测试,而在于您对测试结果的阅读(跳跃:我相信它在您的代码中发现了一个错误)。

可能在实际代码中,您的getListOfIngredientsgetLunchs会执行一些网络请求,即它们与getListOfLunchs和(zip内部的调用异步)。因此,实际代码onStart会立即在调用者线程上调用,而onSucessonEnd稍后会被调用。但是在测试中,您使用非常 同步 Observable.just来模拟这些API调用,因此执行的顺序是不同的:首先调用onSuccess,然后onEnd,最后是onStart(如果您将模拟的callback替换为仅在每次调用中记录方法名称的自定义verifyNoMoreInteractions,则可以轻松验证这一点。

您可能已经成功了,因为您使用onStart时会收到有关onSucess错误顺序的错误消息。不幸的是,这不是它的工作原理。由于您之前已指定订单验证,因此会先检查它们。在这些检查中,没有“不再”的限制。所以发生的事情大致如下:

  1. InOrder被调用。 onStart检查会忽略它,因为还没有onEnd
  2. InOrder被调用。 onStart检查会忽略它,因为还没有onStart
  3. InOrder被调用。这符合onSucess期望的内容,现在等待onSuccess。然而,这(第二个)getListOfIngredients永远不会出现,这正是错误所说的。
  4. 那该怎么办?首先,我想说恕我直言,这次失败的测试确实在您的代码中发现了一个非常真实的错误。假设在将来某个时候有人向您的API添加了一个缓存层,因此有时getLunchsOnRequestListOfLunchsFinished会立即返回同步结果。在这种情况下,您的代码会破坏onStart的合同,callback.onStart(); 应首先调用zip。所以正确的方法是修复你的代码。一个明显但可能错误的方法是移动线

    callback

    到方法的开头。 (为什么它可能是错的?你的onEnd可以抛出异常吗?如果是,那么$a = [ [ "id" => "1", "values" => [ "1", "2" ] ], [ "id" => "2", "values" => [ "1", "3" ] ], [ "id" => "3", "values" => [ "2", "4" ] ], [ "id" => "4", "values" => [ "4", "6" ] ], ]; 的状态会发生什么?另一种方法是对$result = [ [ "id" => "1", "values" => [ "1", "2" ] ], [ "id" => "3", "values" => [ "2", "4" ] ], ]; 执行相同的操作,即以正确的顺序将其复制到成功和错误处理代码中。