我有一个遗留的Java应用程序,它的代码是这样的
ServiceLoader.load(SomeInterface.class)
我想提供一个 SomeInterface 的模拟实现供此代码使用。 我使用模拟模拟框架。
不幸的是,我无法更改旧代码,并且我不希望静态添加任何内容(例如,向 META-INF 添加内容(。
有没有一种简单的方法可以从测试中做到这一点,即在测试运行时?
您可以使用 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));
}
}
来自ServiceLoader.load
文档:
为给定服务类型创建新的服务加载程序,使用 当前线程的上下文类加载器。
因此,您可以在测试运行期间使用特殊的上下文类加载器,该加载器将在META-INF/service
中动态生成提供程序配置文件。上下文类加载器将用于搜索提供程序配置文件,这是由于ServiceLoader
文档中的以下说明:
如果用于提供程序装入的类上下文类加载器装入器的类路径 包括远程网络 URL,然后将在 中取消引用这些 URL 搜索提供程序配置文件的过程。
还需要加载服务类的模拟实现,然后将其作为模拟实现传递。
这样的上下文类加载器需要做两件事:
- 根据请求动态生成提供程序配置文件每
getResource*
方法 - 动态生成类(例如使用 ASM 库(根据
loadClass
方法的请求,如果它是在动态生成的提供程序中指定的类配置文件
使用上述方法,无需更改现有代码。
将调用移动到受保护的方法中,并在测试中重写它。这允许您在测试期间返回任何内容。
服务通常可以在运行时替换。
如果您使用的是 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();
}