如何使用 powermock 部分模拟外部方法调用

how to partially mock an external method call using powermock

我有 2 类 互动。 说 ServiceLayer.classApiAdaptor.class:

public class ApiAdaptor {
  public String getCountry(String latitude, String longitude) {
    // REST call to api goes here and a the country string is returned
    return country;
  }
}

public class ServiceLayer {
 public String findCountry(int positionId) {
   // some business logic here to get latitude and longitude from postitionId 
   ApiAdaptor api = new ApiAdaptor();
   String country = api.getCountry(latitude, longitude);
   return country;
 }
}

现在在单元测试中,我只想测试这个方法ServiceLayer.findcountry(),同时模拟对ApiAdaptor.getCountry(latitude, longitude)的内部调用。有什么办法可以使用 Powermock 来做到这一点。在 Ruby On Rails 中使用 Rspec 时,我看到了类似类型的存根。我也想在我的 java-SpringMVC 项目中进行类似的测试。

当然,您可以使用 PowerMock 只关注该方法。比如具体使用PowerMockito,可以这样写测试:

@RunWith(PowerMockRunner.class)
@PrepareForTest( {ServiceLayer.class} )
public class PowerMockitoJan10Test {
    private static final java.lang.String DESIRED_COUNTRY_VALUE = "USA";

    @Test
    public void testServiceLayerFindCountry() throws Exception {
        ApiAdaptor mock = Mockito.mock(ApiAdaptor.class);
        PowerMockito.whenNew(ApiAdaptor.class).withAnyArguments().thenReturn(mock);
        Mockito.when(mock.getCountry(Mockito.anyString(), Mockito.anyString())).thenReturn(DESIRED_COUNTRY_VALUE);

        String country = new ServiceLayer().findCountry(1);
        Assert.assertEquals(DESIRED_COUNTRY_VALUE, country);
    }
}

如果您使用 Spring,您可能也需要 JUnit 运行程序,因此您可以使用 PowerMockito 的 JUnit 规则来代替 -- see this example.


编辑:这很有趣。使用该规则时,除非您将 ServiceLayer.class 添加到 @PrepareForTest 列表,否则它确实不起作用。在撰写本文时,我使用的是最新的 PowerMockito 版本 1.6.4。可能值得报告。无论如何,这就是您的测试如何使用 Spring:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("mycontext.xml")
@PrepareForTest({ApiAdaptor.class, ServiceLayer.class})
public class PowerMockitoJan10_WithRuleTest {
    private static final String DESIRED_COUNTRY_VALUE = "USA";

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Test
    public void testServiceLayerFindCountry() throws Exception {
        PowerMockito.whenNew(ApiAdaptor.class).withNoArguments().thenReturn(new ApiAdaptor() {
            @Override
            public String getCountry(String latitude, String longitude) {
                return DESIRED_COUNTRY_VALUE;
            }
        });

        String country = new ServiceLayer().findCountry(1);
        Assert.assertEquals(DESIRED_COUNTRY_VALUE, country);
    }

}

或者,如果覆盖有问题,您可以模拟 ApiAdaptor:

    ...
    ApiAdaptor mock = PowerMockito.mock(ApiAdaptor.class);
    PowerMockito.when(mock.getCountry(Mockito.anyString(), Mockito.anyString())).thenReturn(DESIRED_COUNTRY_VALUE);
    PowerMockito.whenNew(ApiAdaptor.class).withNoArguments().thenReturn(mock);
    ...

如果您可以更改代码,我建议您通过对 class.

进行依赖注入来使其更易于测试

你会有这样的东西:

public class ServiceLayer {
    private ApiAdaptor _api;
    public ServiceLayer(ApiAdaptor api) {
        _api = api;
    }
    [snip]
}

然后在其余代码中使用 _api

当你需要测试这个 class 时,如果你必须模拟 ApiAdaptor,你现在可以写:

ApiAdaptor mock = Mockito.mock(ApiAdaptor.class);
[Api adaptor behavior mocking]
String country = new ServiceLayer(mock).findCountry(1);
[asserts and all]

这消除了对 PowerMockito 的需要,它的运行器 and/or 规则…