【问题标题】:Unexpected method call while using PowerMock- EasyMock使用 PowerMock-EasyMock 时出现意外的方法调用
【发布时间】:2014-06-23 08:29:39
【问题描述】:

我的问题似乎与嘲笑的一些误解有关。测试代码。

public class CallHandler {
    private SqlSessionFactory sessionFactory;
    public CallHandler() {
        String resource = "mybatis/mybatis-config.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String handleRequest(Call call) {
        // Some Implementation
    }
}

测试类

// I have excluded few unnecessary classes from @PrepareForTest for the post.

@RunWith(PowerMockRunner.class)
@PrepareForTest({SqlSessionFactoryBuilder.class, Resources.class, SqlSessionFactory.class, SqlSession.class})
public class TestCase0 extends TestCase{
    private SqlSessionFactory mockedSessionFactory = PowerMock.createMock(SqlSessionFactory.class);
    private SqlSession mockedSession = PowerMock.createMock(SqlSession.class);
    private CallMapper mockedMapper = PowerMock.createMock(CallMapper.class);
    private SqlSessionFactoryBuilder mockedSqlSessionFactoryBuilder= PowerMock.createMock(SqlSessionFactoryBuilder.class);
    // Others

    @Before
    public void setUp() {
    }

    @Test
    public void test0 () throws Exception {
        mockStatic(CallMapper.class);
        mockStatic(SqlSessionFactoryBuilder.class);
        mockStatic(Resources.class);
        expect(Resources.getResourceAsReader("mybatis/mybatis-config.xml")).andReturn(mockedReader);      expectNew(SqlSessionFactoryBuilder.class).andReturn(mockedSqlSessionFactoryBuilder);  expect(mockedSqlSessionFactoryBuilder.build(mockedReader)).andReturn(mockedSessionFactory);
        expect(mockedSessionFactory.openSession()).andReturn(mockedSession);
        // Few more expectations
        replayAll();
        assertThat(RESULT0).isEqualTo((new CallHandler()).handleRequest(call));
        verifyAll();
    }
}

这种对新SqlSessionFactoryBuilder 上的构建调用的模拟似乎没有生效,因为原来的build() 被调用,因此出现错误。堆栈跟踪

java.lang.AssertionError: 
Unexpected method call Reader.close();
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
at org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl.invoke(EasyMockMethodInvocationControl.java:91)
at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:105)
at org.powermock.core.MockGateway.methodCall(MockGateway.java:168)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:58)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:38)
at com.kwench.integration.ivr.CallHandler.<init>(CallHandler.java:48)
at in.kwench.integration.ivr.TestCase0.test0(TestCase0.java:131)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:88)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:96)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:294)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:127)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:282)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:86)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:207)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:146)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:33)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:45)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:118)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:104)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:53)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)

【问题讨论】:

    标签: java mocking easymock powermock


    【解决方案1】:

    如果没有您正在测试的代码和测试类本身的完整示例,很难说出问题可能出现在哪里。

    话虽如此,我猜你的问题是你没有准备好使用静态方法的类。这些需要在测试类开始时的 @PrepareForTest 注释中提供。

    PowerMock 的文档提供了以下模拟静态方法的技巧。我猜你错过了第 2 点。Here is the source for this documentation

    模拟静态方法

    快速总结

    1. 在测试用例的类级别使用@RunWith(PowerMockRunner.class) 注释。
    2. 在测试用例的类级别使用@PrepareForTest(ClassThatContainsStaticMethod.class) 注解。
    3. 使用 PowerMock.mockStatic(ClassThatContainsStaticMethod.class) 模拟该类的所有方法。
    4. 使用 PowerMock.replay(ClassThatContainsStaticMethod.class) 将类更改为重播模式。
    5. 使用 PowerMock.verify(ClassThatContainsStaticMethod.class) 将类更改为验证模式。

    您还模拟了SqlSessionFactoryBuilder 的构造函数调用,因此您也需要考虑相关提示。 Here is the documentation for this

    新对象的模拟构造

    快速总结

    1. 在测试用例的类级别使用@RunWith(PowerMockRunner.class) 注释。
    2. 在测试用例的类级别使用@PrepareForTest(ClassThatCreatesTheNewInstance.class) 注解。
    3. 使用 PowerMock.createMock(NewInstanceClass.class) 创建应构造的类的模拟对象(我们称之为 mockObject)。
    4. 使用 PowerMock.expectNew(NewInstanceClass.class).andReturn(mockObject) 来预期 NewInstanceClass.class 类型的对象的新构造,而是返回模拟对象。
    5. 使用 PowerMock.replay(mockObject, NewInstanceClass.class) 将模拟对象和类更改为重播模式,或者使用 PowerMock.replayAll() 方法。
    6. 使用 PowerMock.verify(mockObject, NewInstanceClass.class) 将模拟对象和类更改为验证模式,或者使用 PowerMock.verifyAll() 方法。

    考虑到这些提示,我为您提供的要测试的方法制作了以下测试方法。这个测试通过了。

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({CallHandler.class, SqlSessionFactoryBuilder.class, Resources.class})
    public class CallHandlerTest {
    
        private static final String RESULT0 = "";
    
        @Test
        public void test0 () throws Exception {
            final Reader mockedReader = EasyMock.createMock(Reader.class);
            final SqlSessionFactoryBuilder mockedSqlSessionFactoryBuilder = EasyMock.createMock(SqlSessionFactoryBuilder.class);
            final SqlSessionFactory mockedSessionFactory = EasyMock.createMock(SqlSessionFactory.class);
    
            PowerMock.mockStatic(SqlSessionFactoryBuilder.class);
            PowerMock.mockStatic(Resources.class);
    
            PowerMock.expectNew(SqlSessionFactoryBuilder.class).andReturn(mockedSqlSessionFactoryBuilder);
            EasyMock.expect(Resources.getResourceAsReader("mybatis/mybatis-config.xml")).andReturn(mockedReader);
            EasyMock.expect(mockedSqlSessionFactoryBuilder.build(mockedReader)).andReturn(mockedSessionFactory);
    
            PowerMock.replayAll();
            assertThat(RESULT0).isEqualTo((new CallHandler()).handleRequest(new Call()));
            PowerMock.verifyAll();
        }
    }
    

    【讨论】:

    • 我已将功能更改为完成课程。但是,我认为我不需要在@PreparedTest 中包含CallHandler.class,因为它没有任何静态方法
    • 日志显示错误是因为SqlSessionBuilder.build(..),它存在于给定的代码中,并且有足够的细节。你的测试顺序,没关系吗?如果是这样,那么 PowerMock.expectNew(...) 不应低于第一个 EasyMock.expect(...)
    • @user148015 当对构造函数调用产生期望时(使用expectNew()),您需要在PrepareForTest 中包含调用构造函数的类。因此,在您的情况下,CallHandler 类需要为测试做好准备。有关此示例,请参阅 this other SO postthe documentation for expectNew()。我也会将此添加到我的答案中。
    • 快乐的日子! :) 我只是通过浏览您的问题的解决方案才了解到这一点。太好了,帮助大家再次学习。
    【解决方案2】:

    这是代码覆盖率和powermock.mockStatic的冲突。

    代码覆盖率工具 $jacocoInit 静态方法进入所有类以收集代码覆盖率。运行测试用例时,mockStatic 模拟 $jacocoInit 静态方法,这会导致预期的静态方法调用。

    您可以通过在静态方法上创建部分模拟来绕过该问题,例如PowerMock.mockStaticPartial(TheClass.class, "theStaticMethod") 以便 power mock 不会触及 $jacocoInit 方法。

    【讨论】:

    • 这不是答案。该链接指向一个未解决的 powermock 问题,github.com/jayway/powermock/issues/645
    • 对不起,我的错。我昨天排了太多的审核队列,我错了。我会删除我的评论。
    • Alex,感谢您告诉我这里的规则。实际上,我还没有读过规则。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-06
    • 2016-12-05
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多