在Java中,如何模拟使用ServiceLoader加载的服务?

时间:2015-03-02 08:49:04

标签: java mockito serviceloader

我有一个遗留Java应用程序,其代码类似于此

ServiceLoader.load(SomeInterface.class)

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

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

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

4 个答案:

答案 0 :(得分:6)

来自ServiceLoader.load文档:

  

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

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

  

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

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

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

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

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

答案 1 :(得分:6)

您可以使用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));
    }
}

答案 2 :(得分:2)

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

答案 3 :(得分:0)

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

如果您使用的是 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();
}