Sachin Chandil
Sachin Chandil

Reputation: 17809

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

I am facing a very weird issue here.

Following is my Test 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));
    }

}

here loadFullTextSearch() test cases works perfectly fine until i remove comment of line searchViewModel.loadFullTextSearch_(searchQuery);,

check modified test case:

@Test
public void loadFullTextSearch() throws Exception {

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

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

Here comes the weird part: both the functions(earchViewModel.loadFullTextSearch_(), loadFullTextSearch()) has same code but loadFullTestSearch_() is in SearchViewModel class and loadFullTextSearch() in is test cases it self, that i did to figure out why constructor of DatabaseManager class is not getting mocked(Observable.fromIterable(new DatabaseManager().getGuidelinesListByPositionOnHome())) when code is in SearchViewModel class.

Note: I am mocking constructor of DatabaseManager class. check mockGuidelineList() method. Mocked method is working if constructor is getting invoked in same test class.

Exception i get is :

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

Because i am using dagger and the constructor i am mocking initialises database object dependency.

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

Any help would be appreciated, Thanks.

Upvotes: 0

Views: 626

Answers (1)

Sachin Chandil
Sachin Chandil

Reputation: 17809

I was doing everything fine but missed to add class where constructor is being invoked in @PrepareForTest annotation.

So in my case this has to be:

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

this stackoverflow answer helped me out.

Upvotes: 1

Related Questions