模拟静态对象时使用spring注入的Powermock错误模拟对象



我正在使用PowerMock轻松模拟类的静态方法。我编写了两个测试用例,如果我独立运行,它们运行得很好,但同时运行时会出错。

CarTest:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ ServiceCaller.class })
public class CarTest {
  ServiceCaller           mockServiceCallerObjectToReturn;
  public CarTest() {
    PowerMock.mockStaticPartial(ServiceCaller.class, "getInstance");
    mockServiceCallerObjectToReturn = PowerMock.createMock(ServiceCaller.class);
    EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
  }
  @Test
  public void test1() throws IOException {
    PowerMock.reset(mockServiceCallerObjectToReturn);
    PowerMock.reset(ServiceCaller.class);
    EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
    EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
    PowerMock.replay(mockServiceCallerObjectToReturn);
    PowerMock.replay(ServiceCaller.class);
    Car car = CarFactory.getInstance().getCar();
    boolean canDrive = car.drive("testDriver");
    Assert.assertEquals(canDrive, false);
    PowerMock.verify(mockServiceCallerObjectToReturn);
    PowerMock.verify(ServiceCaller.class);
  }
  @Test
  public void test2() throws IOException {
    PowerMock.reset(mockServiceCallerObjectToReturn);
    PowerMock.reset(ServiceCaller.class);
    EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
    EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
    PowerMock.replay(mockServiceCallerObjectToReturn);
    PowerMock.replay(ServiceCaller.class);
    Car car = CarFactory.getInstance().getCar();
    boolean canDrive = car.drive("testDriver");
    Assert.assertEquals(canDrive, false);
    PowerMock.verify(mockServiceCallerObjectToReturn);
    PowerMock.verify(ServiceCaller.class);
  }
}

CarFactory:

public class CarFactory {    
  private static final String               CAR_SPRING_CONTEXT_XML = "/com/archit/mock/spring-config/CarSpringContext.xml";    
  protected static final ApplicationContext CONTEXT                = new ClassPathXmlApplicationContext(new String[] { CAR_SPRING_CONTEXT_XML });    
  private static final CarFactory           INSTANCE               = new CarFactory();    
  public static CarFactory getInstance() {
    return INSTANCE;
  }    
  public Car getCar() {
    return CONTEXT.getBean("car", Car.class);
  }    
}

汽车:

package com.archit.mock;
public class Car {
  private final ServiceCaller        serviceCaller;
  public Car(final ServiceCallerFactory serviceCallerFactory) {
    this.serviceCaller = serviceCallerFactory.getServiceCaller();
  }
  public boolean drive(final String driver) {
    return (serviceCaller.checkValidity(driver));
  }
}

ServiceCaller:

package com.archit.mock;
public class ServiceCaller {
  private static class ServiceCallerHolder {
    private static ServiceCaller INSTANCE = new ServiceCaller();
  }
  public static ServiceCaller getInstance() {
    return ServiceCallerHolder.INSTANCE;
  }
  public boolean checkValidity(final String x) {
    // Do some call
    throw new IllegalStateException("This should have been mocked");
  }
}

ServiceCallerFactory:

package com.archit.mock;
public class ServiceCallerFactory {
  public ServiceCaller getServiceCaller() {
    return ServiceCaller.getInstance();
  }
}

弹簧配置:

<bean name="car" class="com.archit.mock.Car">
    <constructor-arg>
        <ref bean="serviceCallerFactory" />
    </constructor-arg>
</bean>
<bean name="serviceCallerFactory" class="com.archit.mock.ServiceCallerFactory" />

错误:

java.lang.AssertionError: 
  Unexpected method call ServiceCaller.checkValidity("testDriver"):
    ServiceCaller.checkValidity("testDriver"): expected: 1, actual: 2
  at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
  at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
  at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
  at com.archit.mock.ServiceCaller$$EnhancerByCGLIB$$9848ad9e.checkValidity(<generated>)
  at com.archit.mock.Car.drive(Car.java:12)
  at com.archit.mock.CarTest.test2(CarTest.java:60)
  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:601)
  at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:312)
  at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
  at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:296)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:284)
  at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
  at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:209)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
  at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
  at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
  at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
  at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
  at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
  at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
  at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
  at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

其他观察结果:

  • 将对象范围作为spring-config中的原型很好
  • 单独运行时,两个测试都可以正常工作
  • 基于以上2,重新设置mock似乎是个问题

我发现mock更容易在每次运行测试时将它们作为新对象启动。您可以通过使用Mockito将所有mock对象作为私有变量放在顶部来实现这一点

@Mock
private MockOneClass mockOne;
...
@Mock
private MockNClass mockN;

然后使用JUnitBefore注释,创建一个初始化所有模拟对象的设置函数:

@Before
public void setup() {
    // initialize all the @Mock objects
    MockitoAnnotations.initMocks(this);
}

这样,在每次测试运行之前,您都会创建一个新的Mock,然后您可以对其应用任何数量的expects和功能,而不必担心清除之前测试中完成的任何旧的Mock。如果您有任何Mock,您知道它将提供一些特定的功能(例如静态单例Mock getInstance调用),可以在这个setup函数中调用,以帮助保持Mock重置的测试更干净。

我有一些测试,它有10多个测试,所有测试都在使用这个框架的一行中运行。它不仅使它更容易阅读,而且使从头开始设置测试的速度非常快。能够在测试中复制旧的模拟设置并删除/更改某些部分要容易得多,而在每次测试中都必须维护每个模拟对象,一旦开始进行更多测试,这种情况就无法很好地扩展。

最新更新