L1ghtk3ira
L1ghtk3ira

Reputation: 3189

Java Mockito: Test a protected abstract method

I have read many posts the last few days and there are some that are trying to do what I am trying to accomplish however they are on a class that is extending a class with a protected method. However mine is different.

I currently have a test class that extends and Mocks the abstract class with the protected method. No matter how I try to implement the value for the one keeps returning null and I cannot figure out why this is occurring. In turn this is returning a nullPointerEception rather then my custom exception which is should hit.

My test class is as follows:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest extends AbstractScraperTest<HtmlPage, List<myItems>>{

    @Mock private SsoSetting ssoSetting;
    @Mock private SsoSetting.UrlSettings urlSetting;
    // @Mock(answer = Answers.CALLS_REAL_METHODS) private ServiceForScraping serviceScrape;
    @Mock Scraper scraper;
    @Mock AbstractScraperTest abstractScraperTest;
    @Mock private ProductScrape productScrape;
    @Mock private ProductDetailScrape productDetailScrape;

    @InjectMocks private Service service;

    @Before
    public void setUp() throws Exception {
        when(ssoSetting.getUrls()).thenReturn(urlSetting);
    }

    final String URL = "myUrl";
    final ArgumentCaptor<Map> postRequestCaptor = ArgumentCaptor.forClass(Map.class);
    final ArgumentCaptor<Scraper> scrapeCaptor = ArgumentCaptor.forClass(Scraper.class);

    @Test(expected = customException.class)
    public void scrape_TimeOut_Exception() throws Exception {

    //
    //        AbstractScraperTest ab = Mockito.mock(AbstractScraperTest.class, Mockito.CALLS_REAL_METHODS);
    //
    //    assertEquals(ab.testScraper("htmlResource",
    //                "myUrlToTest"), customException.class);
    //
    //    List<productItems> result = testScraper("htmlResource","myUrlToTest");
    }
}

The abstract class:

public abstract class AbstractScraperTest<Input, Output> {
    public ServiceForScraping serviceScrape;
    public Scraper<Input, Output> scraper;

    protected abstract Scraper<Input, Output> getScraper();

    @Before
    public void setUp() throws Exception {
        scraper = getScraper();
        serviceScrape = new ServiceForScraping ();
        ServiceForScraping .init();
    }

    protected Output testScraper(String filePath, String url) throws Exception {
        InputStream input = getClass().getResourceAsStream(filePath);
        String html = IOUtils.toString(input);

        return serviceScrape.scrapeString(url, html, scraper);
    }
}

The Class testing for the Exception:

public abstract class myScraper<Output> implements     HTMLUnitScraper<Output> {

    @Override
    public Output scrape(HtmlPage page, String scraperUrl) throws Exception  {
        checkSessionTimeout(page, scraperUrl);
        return scrapeHtmlPage(page, scraperUrl);
    }

    private void checkSessionTimeout(HtmlPage page, String scraperUrl) throws Exception {

        if (page.getFirstByXPath("//classImLookingFor']") != null
            && page.getFirstByXPath("//ClassImLookingFor") != null) {
            throw new customExceptionThrown("Session Timeout" + scraperUrl);
        }
    }

    public abstract Output scrapeHtmlPage(HtmlPage page, String scraperUrl)  throws Exception;
}

The abstract classes method 'testScrape' tests if the html resource contains timeout session information. I have used this method in other tests that do not use Mockito and they have all worked:

Example

@Test(expected=customException.class)
    public void scrape_TimeOut_Exception() throws Exception {
    List<CartItem> result = testScraper("htmlResource","myUrl");
}

The test should validate to true as the html resource does contain session timeout information.

The problem I believe when I debug on

return serviceScrape.scrapeString(url, html, scraper);

The scraper returns null. I have tried doing things such as

AbstractScraperTest.scraper = scraper;

as well moving the calls in the @before in abstractScraperTest into the @before in the ServiceTest class however that does not seem to work as well. I am not sure why I am returning null and cant put a value in it, I believe this is why it is failing.

Links Looked into that I remember:

  1. mocking protected method
  2. How to mock protected subclass method inherited from abstract class?
  3. How can I test a method which invoke protected (unwanted) methods of parent class?
  4. http://huahsin68.blogspot.ca/2014/01/invoke-protected-method-with.html
  5. https://groups.google.com/forum/#!topic/powermock/DtMMHa9k-4Q
  6. http://www.vogella.com/tutorials/Mockito/article.html

I did read into one post that was saying to use PowerMockito however I could not get the methods and implementations for that to work.

I am fairly sure this has to be possible for what I am trying to achieve as other ones are fairly similar however I cannot figure the correct implementation for it.

Upvotes: 1

Views: 1779

Answers (1)

Lorenzo Murrocu
Lorenzo Murrocu

Reputation: 688

serviceScrape.scrapeString(url, html, scraper) is returning null because the test is calling the mock, and there isn't any where instruction, so the default behavior is to return null.

Looking at the commented lines, I think that your goal is to use partial mocks and call the real methods by default, isn't it? The way to do this is:

@Mock(answer=Answers.CALLS_REAL_METHODS) private ServiceForScraping serviceScrape;

EDIT =======

This is the result after a discussion in the comments. I totally misunderstood the problem... Now I see what's the problem and looking at the commented lines in the method scrape_TimeOut_Exception(), my answer is this:

Mockito is intended to mock the collaborators of the class you are testing, not the test itself.

In your case, you should just implement the method getScraper() in the class ServiceTest:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest extends AbstractScraperTest<HtmlPage, List<myItems>>{

    @Mock private SsoSetting ssoSetting;
    @Mock private SsoSetting.UrlSettings urlSetting;
    // @Mock(answer = Answers.CALLS_REAL_METHODS) private ServiceForScraping serviceScrape;
    @Mock Scraper scraper;
    @Mock private ProductScrape productScrape;
    @Mock private ProductDetailScrape productDetailScrape;

    @Before
    public void setUp() throws Exception {
        when(ssoSetting.getUrls()).thenReturn(urlSetting);
    }

    @Override
    protected Scraper<Input, Output> getScraper()
    {
        return this.scraper;
    }

    final String URL = "myUrl";
    final ArgumentCaptor<Map> postRequestCaptor = ArgumentCaptor.forClass(Map.class);
    final ArgumentCaptor<Scraper> scrapeCaptor = ArgumentCaptor.forClass(Scraper.class);

    @Test(expected = customException.class)
    public void scrape_TimeOut_Exception() throws Exception {

    // TODO Mocks should be configured like SsoSetting
    //        when(this.scraper.someMethod()).thenReturn(someOutput);
    // ...

        assertEquals(ab.testScraper("htmlResource",
                    "myUrlToTest"), customException.class);

        List<productItems> result = testScraper("htmlResource","myUrlToTest");
    }
}

the abstract class:

public abstract class AbstractScraperTest<Input, Output> {
    @InjectMocks public ServiceForScraping serviceScrape;
    public Scraper<Input, Output> scraper;

    protected abstract Scraper<Input, Output> getScraper();

    @Before
    public void setUp() throws Exception {
        scraper = getScraper();
        serviceScrape = new ServiceForScraping ();
        ServiceForScraping .init();
    }

    protected Output testScraper(String filePath, String url) throws Exception {
        InputStream input = getClass().getResourceAsStream(filePath);
        String html = IOUtils.toString(input);

        return serviceScrape.scrapeString(url, html, scraper);
    }
}

It would be even better if the abstract method is not used at all:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest extends AbstractScraperTest<HtmlPage, List<myItems>>{

    @Mock private SsoSetting ssoSetting;
    @Mock private SsoSetting.UrlSettings urlSetting;
    // @Mock(answer = Answers.CALLS_REAL_METHODS) private ServiceForScraping serviceScrape;
    // use the one from the super @Mock Scraper scraper;
    @Mock private ProductScrape productScrape;
    @Mock private ProductDetailScrape productDetailScrape;

    @Before
    public void setUp() throws Exception {
        when(ssoSetting.getUrls()).thenReturn(urlSetting);
    }

    final String URL = "myUrl";
    final ArgumentCaptor<Map> postRequestCaptor = ArgumentCaptor.forClass(Map.class);
    final ArgumentCaptor<Scraper> scrapeCaptor = ArgumentCaptor.forClass(Scraper.class);

    @Test(expected = customException.class)
    public void scrape_TimeOut_Exception() throws Exception {

    // TODO Mocks should be configured like SsoSetting
    //        when(super.scraper.someMethod()).thenReturn(someOutput);
    // maybe set something in the tested instance:
    // super.serviceScrape.setSomething(this.something);
    // ...

        assertEquals(ab.testScraper("htmlResource",
                    "myUrlToTest"), customException.class);

        List<productItems> result = testScraper("htmlResource","myUrlToTest");
    }
}

the abstract class:

public abstract class AbstractScraperTest<Input, Output> {
    @InjectMocks public ServiceForScraping serviceScrape;
    @Mock public Scraper<Input, Output> scraper;

    @Before
    public void setUp() throws Exception {
        serviceScrape = new ServiceForScraping ();
        ServiceForScraping .init();
    }

    protected Output testScraper(String filePath, String url) throws Exception {
        InputStream input = getClass().getResourceAsStream(filePath);
        String html = IOUtils.toString(input);

        return serviceScrape.scrapeString(url, html, scraper);
    }
}

Note that in this way, each test extending the abstract class may configure all the mocks (also the inherited ones) as needed.

I hope this helps and sorry for my misunderstanding!

Upvotes: 1

Related Questions