13

我有一个遗留 Java 应用程序,其中包含类似这样的代码

ServiceLoader.load(SomeInterface.class)

我想提供 SomeInterface 的模拟实现供此代码使用。我使用 mockito 模拟框架。

不幸的是,我无法更改遗留代码,并且我不希望静态添加任何内容(例如,向 META-INF 添加内容)。

有没有一种简单的方法可以在测试中做到这一点,即。在测试运行时?

4

4 回答 4

8

您可以使用PowerMockito和 Mockito 来模拟静态方法:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ServiceLoader.class)
public class PowerMockingStaticTest
{
    @Mock
    private ServiceLoader mockServiceLoader;

    @Before
    public void setUp()
    {
        PowerMockito.mockStatic(ServiceLoader.class);
        Mockito.when(ServiceLoader.load(Mockito.any(Class.class))).thenReturn(mockServiceLoader);
    }

    @Test
    public void test()
    {
        Assert.assertEquals(mockServiceLoader, ServiceLoader.load(Object.class));
    }
}
于 2015-03-02T09:26:35.847 回答
7

ServiceLoader.load文档中:

使用当前线程的上下文类加载器为给定的服务类型创建一个新的服务加载器。

因此,您可以在测试运行期间使用特殊的上下文类加载器,它将在META-INF/service. 由于文档中的此注释,上下文类加载器将用于搜索提供程序配置文件ServiceLoader

如果用于提供程序加载的类加载器的类路径包括远程网络 URL,那么这些 URL 将在搜索提供程序配置文件的过程中被取消引用。

上下文类加载器还需要加载服务类的模拟实现,然后作为模拟实现传递。

这样的上下文类加载器需要做两件事:

  • getResource*根据每个方法的请求动态生成提供者配置文件
  • 根据每个方法的请求动态生成一个类(例如使用ASM 库loadClass) ,如果它是在动态生成的提供程序配置文件中指定的类

使用上述方法,您无需更改现有代码。

于 2015-03-02T09:11:30.933 回答
2

将调用移动到受保护的方法中并在测试中覆盖它。这允许您在测试期间返回任何内容。

于 2015-03-02T08:51:34.960 回答
-1

服务通常可以在运行时被替换。

如果您使用的是OSGi,您可以在使用注释的设置方法中替换服务实现,@BeforeClass并在方法中取消注册模拟实现@AfterClass

private ServiceRegistration m_registration;

@BeforeClass
public void setUp() {
  SomeInterface mockedService = Mockito.mock(SomeInterface.class);
  m_registration = registerService(Activator.getDefault().getBundle(), Integer.MAX_VALUE, SomeInterface.class, mockedService);
}

@AfterClass
public void tearDown() {
  if (m_registration != null) {
    unregisterService(m_registration);
  }
}

public static ServiceRegistration registerService(Bundle bundle, int ranking, Class<? extends IService> serviceInterface, Object service) {
  Hashtable<String, Object> initParams = new Hashtable<String, Object>();
  initParams.put(Constants.SERVICE_RANKING, ranking);
  return bundle.getBundleContext().registerService(serviceInterface.getName(), service, initParams);
}

public static void unregisterService(ServiceRegistration registration) {
  registration.unregister();
}
于 2015-03-02T09:38:01.913 回答