Java Mockito:测试受保护的抽象方法

时间:2016-06-27 21:16:07

标签: java unit-testing junit mockito powermockito

我在过去几天看了很多帖子,有些人试图做我想要完成的事情,但是他们正在使用受保护方法扩展类的类。然而我的不同。

我目前有一个扩展的测试类,并使用受保护的方法模拟抽象类。无论我如何尝试实现一个值保持返回null,我无法弄清楚为什么会发生这种情况。反过来,这将返回一个nullPointerEception,而不是我应该命中的自定义异常。

我的测试类如下:

@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");
    }
}

抽象类:

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);
    }
}

异常的类测试:

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;
}

抽象类方法'testScrape'测试html资源是否包含超时会话信息。我在其他不使用Mockito的测试中使用过这种方法,并且它们都有效:

示例

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

测试应验证为true,因为html资源确实包含会话超时信息。

我在调试

时遇到的问题
return serviceScrape.scrapeString(url, html, scraper);

scraper返回null。我尝试过像

这样的事情
AbstractScraperTest.scraper = scraper;

同时将abstractScraperTest中的@before中的调用移动到ServiceTest类中的@before,但是这似乎不起作用。我不知道为什么我要返回null并且不能在其中加入值,我相信这就是它失败的原因。

链接看起来我记得:

  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
  7. 我确实读过一篇说要使用PowerMockito的文章但是我无法获得该方法和实现。

    我很确定这对于我想要达到的目标是可能的,因为其他的非常类似,但我无法为它找到正确的实现。

1 个答案:

答案 0 :(得分:1)

serviceScrape.scrapeString(url, html, scraper)返回null,因为测试正在调用mock,并且没有任何where指令,因此默认行为是返回null。

查看注释行,我认为您的目标是使用部分模拟并默认调用实际方法,不是吗?这样做的方法是:

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

编辑=======

这是评论中讨论后的结果。我完全误解了这个问题......现在我看到问题是什么,并查看方法scrape_TimeOut_Exception()中的注释行,我的答案是这样的:

Mockito旨在嘲笑您正在测试的类的合作者,而不是测试本身。

在您的情况下,您应该在课程getScraper()中实施方法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");
    }
}

抽象类:

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);
    }
}

如果根本不使用抽象方法会更好:

@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");
    }
}

抽象类:

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);
    }
}

请注意,通过这种方式,扩展抽象类的每个测试都可以根据需要配置所有模拟(也包括继承的模拟)。

我希望这对我的误解有所帮助和抱歉!