【问题标题】:Verify mock interactions within anonymous inner class验证匿名内部类中的模拟交互
【发布时间】:2016-02-24 10:32:54
【问题描述】:

我正在尝试在我的应用程序中测试我的 ViewModel,这是构造函数:

@Inject
public SearchUserViewModel(@Named("searchUser") UseCase searchUserUseCase) {
  this.searchUserUseCase = searchUserUseCase;
}

在我的测试中,我使用这样的模拟创建了一个 SearchUserUseCase:

Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);

when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());

searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);

在我的 ViewModel 类中,我有这个要测试的 sn-p:

public void onClickSearch(View view) {
  loadUsers();
}

private void loadUsers() {
  if (username == null) {
    fragmentListener.showMessage("Enter a username");
  } else {
    showProgressIndicator(true);
    searchUserUseCase.execute(new SearchUserSubscriber(), username);
  }
}

private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {

  @Override
  public void onCompleted() {
    showProgressIndicator(false);
  }

  @Override
  public void onError(Throwable e) {
    showProgressIndicator(false);
    fragmentListener.showMessage("Error loading users");
  }

  @Override
  public void onNext(SearchResponse searchResponse) {
    List<User> users = searchResponse.getUsers();
    if (users.isEmpty()) {
      fragmentListener.showMessage("No users found");
    } else {
      fragmentListener.addUsers(users);
    }
  }

}

最后在我的测试中我有这个:

@Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
  SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
  searchUserViewModel.setFragmentListener(mockFragmentListener);
  searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
  searchUserViewModel.onClickSearch(view);
  verify(mockFragmentListener).showMessage("Error loading users");
}

我从 Mockito 收到此错误:

Wanted but not invoked:
fragmentListener.showMessage(
  "Error loading users"
);

我不确定这是否是一个好的测试,但我想以某种方式测试 SearchUserSubscriber。谢谢

编辑:我在这里找到了与此问题类似的问题:Can't verify mock method call from RxJava Subscriber(仍然没有回答)和这里:Verify interactions in rxjava subscribers。后一个问题类似,但不会在单独的类中执行订阅者(发生在 SearchUserUseCase 此处)。

我还尝试了 RobolectricGradleTestRunner 而不是 MockitoJunitRunner 并更改为 Schedulers.io() 和 AndroidSchedulers.mainThread(),但我仍然遇到相同的错误。

尝试模拟 SearchUserUseCase 而不是 GitHubService(感觉更干净),但我不确定如何以这种方式测试订阅者,因为它作为参数传递给 UseCase 中的 void 方法 execute()。

public void execute(Subscriber useCaseSubscriber, String query) {
  subscription = buildUseCase(query)
    .observeOn(postExecutionThread.getScheduler())
    .subscribeOn(threadExecutor.getScheduler())
    .subscribe(useCaseSubscriber);
}

还有 buildUseCase()

@Override
public Observable buildUseCase(String username) throws NullPointerException {
  if (username == null) {
    throw new NullPointerException("Query must not be null");
  }
  return getGitHubService().searchUser(username);
}

【问题讨论】:

  • 我不知道最初是什么问题,但经过一些来回重构后它现在可以工作了。我最终得到了与问题中完全相同的代码。

标签: android testing mocking mockito


【解决方案1】:

对我来说,添加Observable.Transformer&lt;T, T&gt; 如下:

void gatherData() {
    service.doSomeMagic()
      .compose(getSchedulerTransformer())
      .subscribe(view::displayValue);
}

private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
    if (mTransformer == null) {
        mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    return mTransformer;
}

void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
    mTransformer = transformer;
}

并设置变压器。我刚刚通过了这个:

setSchedulerTransformer(observable -> {
    if (observable instanceof Observable) {
        Observable observable1 = (Observable) observable;
        return observable1.subscribeOn(Schedulers.immediate())
                .observeOn(Schedulers.immediate());
    }
    return null;
});

因此,只需在您的测试中添加一个@Before 方法并调用presenter.setSchedulerTransformer,它应该能够对此进行测试。如果您想了解更多详细信息,请查看this 答案。

【讨论】:

    【解决方案2】:

    如果您使用的是 Mockito,您可能可以使用 ArgumentCaptor 获取 SearchUserSubscriber,例如...

    @Captor
    private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;
    
    private SearchUserSubscriber getSearchUserSubscriber() {
    
        // TODO: ...set up the view model...
        ...
        // Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
        viewModel.onClickSearch(view);
    
        verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));
    
        return subscriberCaptor.getValue();
    }
    

    现在您可以拥有测试用例,例如...

    @Test
    public void shouldDoSomethingWithTheSubscriber() {
        SearchUserSubscriber subscriber = getSearchUserSubscriber();
    
        ...
    }
    

    【讨论】:

    • 问题是 SearchUserSubscriber 是一个私有类。它确实扩展了 DefaultSubscriber,而 DefaultSubscriber 又扩展了 rx.Subscriber。我按照您的示例实现了 DefaultSubscriber,但除了检查它是否已订阅之外,我无法从中获得任何好的信息。我想验证 mockFragmentListener 但这似乎很难......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-09
    • 1970-01-01
    • 2012-08-14
    • 2011-07-06
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多