使用Mockito和间谍进行单位测试,导致错误



我正在使用 Mockito并间谍来测试功能。

这是正在测试的类:

public class RecipeListModelImp
        implements RecipeListModelContract {
    private Subscription subscription;
    private RecipesAPI recipesAPI;
    @Inject
    public RecipeListModelImp(@NonNull RecipesAPI recipesAPI) {
        this.recipesAPI = Preconditions.checkNotNull(recipesAPI);
    }
    @Override
    public void getRecipesFromAPI(final RecipeGetAllListener recipeGetAllListener) {
        subscription = recipesAPI.getAllRecipes()
               .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<List<Recipe>>() {
                    @Override
                    public void onCompleted() {
                              }
                    @Override
                    public void onError(Throwable e) {
                                  recipeGetAllListener.onRecipeGetAllFailure(e.getMessage());
                    }

                    @Override
                    public void onNext(List<Recipe> recipe) {                       recipeGetAllListener.onRecipeGetAllSuccess(recipe);
                    }
                });
    }
    @Override
    public void shutdown() {
        if(subscription != null && !subscription.isUnsubscribed()) {
            subscription.unsubscribe();
        }
    }
}

我正在尝试使用Mockito和Spy进行测试,因为我不想调用真实功能recipesAPI.getAllRecipes()只是验证它。该测试称为testGetRecipesFromAPI()

public class RecipeListModelImpTest {
    @Mock Subscription subscription;
    @Mock RecipesAPI recipesAPI;
    @Mock RecipeListModelContract.RecipeGetAllListener recipeGetAllListener;
    private RecipeListModelContract recipeListModel;
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(RecipeListModelImpTest.this);
        recipeListModel = new RecipeListModelImp(recipesAPI);
    }
    @Test
    public void testGetRecipesFromAPI() {
        RecipeListModelContract recipeListModelSpy = spy(recipeListModel);
        RecipesAPI recipeApiSpy = spy(recipesAPI);
        doNothing().when(recipeApiSpy).getAllRecipes();
        recipeListModelSpy.getRecipesFromAPI(recipeGetAllListener);
        verify(recipesAPI, times(1)).getAllRecipes();
    }
    @Test
    public void testShouldShutdown() {
        recipeListModel.shutdown();
        verify(subscription, times(1)).unsubscribe();
    }
}

这是错误:

org.mockito.exceptions.base.MockitoException: 
Only void methods can doNothing()!
Example of correct use of doNothing():
    doNothing().
    doThrow(new RuntimeException())
    .when(mock).someVoidMethod();
Above means:
someVoidMethod() does nothing the 1st time but throws an exception the 2nd time is called

我也尝试过导致无效指针的此方法:

  @Test
    public void testGetRecipesFromAPI() {
        RecipeListModelContract recipeListModelSpy = spy(recipeListModel);
        RecipesAPI recipeApiSpy = spy(recipesAPI);
        doReturn(Observable.just(Subscription.class)).when(recipeApiSpy).getAllRecipes();
        recipeListModelSpy.getRecipesFromAPI(recipeGetAllListener);
        verify(recipesAPI, times(1)).getAllRecipes();
    }

代码中的问题是此部分: subscribeOn(Schedulers.io())。如果只有我们可以消除这一点,那么我们可以从recipesAPI返回测试数据,并测试recipeGetAllListener是否正确处理此数据。

因此,我们必须以某种方式创建一个接缝:如果这是生产代码 - 然后使用Schedulers.io()/AndroidSchedulers.mainThread(),如果这是测试代码 - 然后使用一些特定的调度程序。

让我们声明一个接口,它将提供Scheduler S:


    interface SchedulersProvider {
        Scheduler getWorkerScheduler();
        Scheduler getUiScheduler();
    }

现在,让我们使RecipeListModelImpSchedulersProvider有依赖性:


    public class RecipeListModelImp implements RecipeListModelContract {
        ...
        private SchedulersProvider schedulersProvider;
        @Inject
        public RecipeListModelImp(@NonNull RecipesAPI recipesAPI, 
                                  @NonNull SchedulersProvider schedulerProvider) {
            ...
            this.schedulersProvider = schedulersProvider;
        }
        ...
    }

现在,我们将替换调度程序:


    @Override
    public void getRecipesFromAPI(final RecipeGetAllListener recipeGetAllListener) {
        subscription = recipesAPI.getAllRecipes()
                                 .subscribeOn(schedulersProvider.getWorkerScheduler())
                                 .observeOn(schedulersProvider.getUiScheduler())
                                 ...
    }

是时候照顾提供SchedulerProvider


    @Module
    public class MyModule {
        ...
        @Provides public SchedulerProvider provideSchedulerProvider() {
            return new SchedulerProvider() {
                @Override
                Scheduler getWorkerScheduler() {
                    return Schedulers.io();
                }
                @Override
                Scheduler getUiScheduler() {
                    return AndroidSchedulers.mainThread();
                }
            }
        }
    }

现在,让我们创建另一个模块-TestModule,它将为测试类提供依赖项。TestModule将扩展MyModule并覆盖提供SchedulerProvider的方法:


    public class TestModule extends MyModule {
        @Override public SchedulerProvider provideSchedulerProvider() {
            return new SchedulerProvider() {
                @Override
                Scheduler getScheduler() {
                    return Schedulers.trampoline();
                }
                @Override
                Scheduler getUiScheduler() {
                    return Schedulers.trampoline();
                }
            }
        }
    }

Schedulers.trampoline()将在当前线程上执行任务。

是时候创建测试组件:


    @Component(modules = MyModule.class)
    public interface TestComponent extends MyComponent {
        void inject(RecipeListModelImpTest test);
    }

现在在测试类中:


    public class RecipeListModelImpTest {
        @Mock RecipesAPI recipesAPI;
        @Mock RecipeListModelContract.RecipeGetAllListener recipeGetAllListener;
        @Inject SchedulerProvider schedulerProvider;
        private RecipeListModelContract recipeListModel;
        @Before
        public void setup() {
            TestComponent component = DaggerTestComponent.builder()
                                                         .myModule(new TestModule())
                                                         .build();
            component.inject(this);
            MockitoAnnotations.initMocks(this);
            recipeListModel = new RecipeListModelImp(recipesAPI, schedulerProvider);
        }
        ...
    }

和实际测试部分:

    private static final List<Recipe> TEST_RECIPES = new ArrayList<Recipe>() {
        {
            add(new Recipe(1)),
            add(new Recipe(2))
        }
    };
    @Test
    public void testGetRecipesFromAPI() {
        when(recipeAPI.getAllRecipes())
            .thenReturn(Observable.fromIterable(TEST_RECIPES));
        recipeListModel.getRecipesFromAPI(recipeGetAllListener);
        // verifying, that `recipeAPI.getAllRecipes()` has been called once
        verify(recipeAPI).getAllRecipes();
        // verifying, that we received correct result
        verify(recipeGetAllListener).onRecipeGetAllSuccess(TEST_RECIPES);
    }

您写

subscription = copperesapi.getAllRecipes((。订阅(schedulers.io(((

然后methot getallrecipes((返回一些对象,而您不能使用

donothing((。何时(配方(.getAllRecipes((;

donothing(( - 用于方法返回void。

变体是正确的:

doreturn(doreturn(observable.just(subscription.class((。何时(refeceapispy(.getAllRecipes((

您正在混合间谍(部分模拟(和模拟(完整模拟(。这是不必要的-Spy允许您混合模拟和真实方法调用,但是您不需要任何部分模拟。在您的情况下,您要么是完全嘲笑,要么没有嘲笑。Mockito的文档具有有关嘲笑和间谍的更多信息。

在您的第一个示例中,错误是您试图在返回某些内容的方法上 doNothing。Mockito不允许这样做。您在第二个示例中所做的几乎是正确的。

在第二个示例中,问题是您设置了getAllRecipes()返回Observable.just(Subscription.class),但是在测试的单元中,您仍然将整个方法链都调用:subscribeOnobserveOnsubscribe。您还需要模拟这些呼叫才能返回可以使用的模拟对象,或者用thf thf thf thf thf thur nullpoInterException。

@Test
public void testGetRecipesFromAPI() { 
    //recipesAPI.getAllRecipes() needs to be mocked to return something (likely a mock)
    // so subscribeOn can be called.
    //That needs to be mocked to return something so observeOn can be called
    //etc.
    recipeListModel.getRecipesFromAPI(recipeGetAllListener);
    verify(recipesAPI, times(1)).getAllRecipes();
}

相关内容

  • 没有找到相关文章

最新更新