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

时间:2013-08-13 00:35:59

标签: java unit-testing junit easymock powermock

我正在使用PowerMock easy mock来模拟类的静态方法。我写了两个测试用例,如果我独立运行运行正常,但在同时运行时给我一个错误。

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

Spring Config:

<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,重新设置模拟似乎存在问题。

1 个答案:

答案 0 :(得分:1)

我发现使用模拟更容易让它们在每次运行测试时都以新对象的形式启动。您可以使用Mockito.Mock注释将顶部的所有模拟对象作为私有变量来实现此目的:

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

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

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

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

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