PowerMockito:如果代码在另一个 class 中,则无法模拟构造函数

PowerMockito: Unable to mock a constructor if code is in another class

我遇到了一个很奇怪的问题。

以下是我的测试class:

SearchViewModel.java

@RunWith(PowerMockRunner.class)
@PrepareForTest({JSONReader.class, ConstantsPath.class, DatabaseManager.class})
public class SearchViewModelTest {

    @Rule
    public TestRule rule = new InstantTaskExecutorRule();
    private String indexSearchContent;
    private String fullTextSearchContentGL1, fullTextSearchContentGL2, fullTextSearchContentGL3, fullTextSearchContentGL4;
    private String searchQuery = "a";
    private List<FullTextSearchItem> fullTextSearchResult;
    private String behaviorString;
    private SearchViewModel searchViewModel;
    private DatabaseManager databaseManager;

    private void initInputs() throws IOException {
        indexSearchContent = JSONReader.convertStreamToString(getClass().getClassLoader().getResourceAsStream(ConstantsPath.getInstance().getIndexSearchFilePath()));
        behaviorString = JSONReader.convertStreamToString(getClass().getClassLoader().getResourceAsStream(ConstantsPath.getInstance().getBehavioralFilePath()));

        fullTextSearchContentGL1 = JSONReader.convertStreamToString(getClass().getClassLoader().getResourceAsStream(ConstantsPath.getInstance().getFullTextSearchFilePath("1")));
        fullTextSearchContentGL2 = JSONReader.convertStreamToString(getClass().getClassLoader().getResourceAsStream(ConstantsPath.getInstance().getFullTextSearchFilePath("2")));
        fullTextSearchContentGL3 = JSONReader.convertStreamToString(getClass().getClassLoader().getResourceAsStream(ConstantsPath.getInstance().getFullTextSearchFilePath("3")));


    }

    private void mockDaggerDependency() {
        AppInfo appInfo = Mockito.mock(AppInfo.class);
        Mockito.when(appInfo.getAppName()).thenReturn("testApp");
        Mockito.when(appInfo.getAppLanguage()).thenReturn("EN");

        TestApplicationModule module = new TestApplicationModule(appInfo);
        DatabaseModule databaseModule = Mockito.mock(DatabaseModule.class);
        Component component = DaggerComponent.builder().applicationModule(module).databaseModule(databaseModule).build();
        MyApplication.setComponent(component);
    }

    private void mockGuidelineList() throws Exception {
        databaseManager = PowerMockito.mock(DatabaseManager.class);
        List<Guideline> mockedGls = new ArrayList<>();
        Guideline gl = new Guideline();
        gl.setGuidelineId("1");
        mockedGls.add(gl);
        gl = new Guideline();
        gl.setGuidelineId("2");
        mockedGls.add(gl);
        gl = new Guideline();
        gl.setGuidelineId("3");
        mockedGls.add(gl);
        Mockito.when(databaseManager.getGuidelinesListByPositionOnHome()).thenReturn(mockedGls);

        PowerMockito.whenNew(DatabaseManager.class).withNoArguments().thenReturn(databaseManager);
        // prepare expected output for fulltext search
        Observable.fromIterable(new DatabaseManager().getGuidelinesListByPositionOnHome())
                .map(Guideline::getGuidelineId)
                .flatMap(glId -> BehavioralFile.<List<FullTextSearchItem>>loadJsonFile(ConstantsPath.getInstance().getFullTextSearchFilePath(glId),
                        new TypeToken<List<FullTextSearchItem>>() {
                        }.getType()).toObservable()
                        .flatMapIterable(fullTextSearchitems -> fullTextSearchitems)
                        .filter(item -> item.getText().toLowerCase().contains(searchQuery.toLowerCase()))).<List<FullTextSearchItem>>toList()
                .subscribe(list -> {
                    fullTextSearchResult = list;
                });
    }

    @Before
    public void setUp() throws Exception {
        MainActivityTest.overrideRxJavaPlugins();
        mockDaggerDependency();
        initInputs();

        PowerMockito.mockStatic(JSONReader.class);
        BDDMockito.given(JSONReader.readJsonFile(ConstantsPath.getInstance().getIndexSearchFilePath())).willReturn(indexSearchContent);

        BDDMockito.given(JSONReader.readJsonFile(ConstantsPath.getInstance().getFullTextSearchFilePath("1"))).willReturn(fullTextSearchContentGL1);
        BDDMockito.given(JSONReader.readJsonFile(ConstantsPath.getInstance().getFullTextSearchFilePath("2"))).willReturn(fullTextSearchContentGL2);
        BDDMockito.given(JSONReader.readJsonFile(ConstantsPath.getInstance().getFullTextSearchFilePath("3"))).willReturn(fullTextSearchContentGL3);

        BDDMockito.given(JSONReader.readJsonFile(ConstantsPath.getInstance().getBehavioralFilePath())).willReturn(behaviorString);

        mockGuidelineList();

        searchViewModel = new SearchViewModel();
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void loadFullTextSearch() throws Exception {

        //searchViewModel.loadFullTextSearch_(searchQuery);
        loadFullTextSearch(searchQuery);

        assertEquals(searchViewModel.fullTextSearchListLiveData.getValue().size(), fullTextSearchResult.size());
    }

    private void loadFullTextSearch(String query) {
       // following line is throwing exception if put in another class.
        Observable.fromIterable(new DatabaseManager().getGuidelinesListByPositionOnHome())  
                .map(Guideline::getGuidelineId)
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(Schedulers.io())
                .flatMap(glId -> BehavioralFile.<List<FullTextSearchItem>>loadJsonFile(ConstantsPath.getInstance().getFullTextSearchFilePath(glId),
                        new TypeToken<List<FullTextSearchItem>>() {
                        }.getType()).toObservable()
                        .flatMapIterable(fullTextSearchitems -> fullTextSearchitems)
                        .filter(item -> item.getText().toLowerCase().contains(query.toLowerCase()))).<List<FullTextSearchItem>>toList().toObservable()
                .subscribe(list -> searchViewModel.fullTextSearchListLiveData.setValue(list));
    }

}

这里 loadFullTextSearch() 测试用例工作得很好,直到我删除行 searchViewModel.loadFullTextSearch_(searchQuery);,

的注释

检查修改后的测试用例:

@Test
public void loadFullTextSearch() throws Exception {

        searchViewModel.loadFullTextSearch_(searchQuery);
        //loadFullTextSearch(searchQuery);

        assertEquals(searchViewModel.fullTextSearchListLiveData.getValue().size(), fullTextSearchResult.size());
    }

奇怪的部分来了: 两个函数(earchViewModel.loadFullTextSearch_()loadFullTextSearch())具有相同的代码,但 loadFullTestSearch_() 在 SearchViewModel class 和 loadFullTextSearch() 是测试用例本身,我这样做是为了弄清楚为什么 DatabaseManager class 的构造函数在代码位于 SearchViewModel class.

注意: 我正在模拟 DatabaseManager class 的构造函数。检查 mockGuidelineList() 方法。如果在同一个测试中调用构造函数,则模拟方法有效 class。

我得到的异常是:

java.lang.NullPointerException: Cannot return null from a non-@Nullable @Provides method

因为我正在使用 dagger 并且我正在模拟的构造函数初始化数据库对象依赖项。

public DatabaseManager() {
    MyApplication.getComponent().inject(this);
}

任何帮助将不胜感激,谢谢。

我做的一切都很好但是错过了添加 class 在 @PrepareForTest 注释中调用构造函数的地方。

所以在我的情况下,这必须是:

@PrepareForTest({JSONReader.class, ConstantsPath.class, DatabaseManager.class, SearchViewModel.class})

this Whosebug 的回答帮助了我。