I have a legacy Java application that has code something like this
ServiceLoader.load(SomeInterface.class)
and I want to provide a mock implementation of SomeInterface for this code to use. I use the mockito mocking framework.
Unfortunately I am unable to change the legacy code, and I do not wish to add anything statically (eg. adding things to META-INF).
Is there an easy way to do this from within the test, ie. at runtime of the test?
You can use PowerMockito along with Mockito to mock static methods:
@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));
}
}
From the ServiceLoader.load
documentation:
Creates a new service loader for the given service type, using the
current thread's context class loader.
So you could use a special context class loader during test runs that will dynamically generate provider-configuration files in META-INF/service
. The context class loader will be used for searching for provider-configuration files due to this note in the ServiceLoader
documentation:
If the class path of a class loader that is used for provider loading
includes remote network URLs then those URLs will be dereferenced in
the process of searching for provider-configuration files.
The context class loader needs to also load a mock implementation of the service class, which is then passed as the mock implementation.
Such a context class loader would need to do two things:
- dynamically generating the provider configuration files on request
per
getResource*
methods
- dynamically generate a class (for example
using ASM library) on request per
loadClass
methods, if it is the
class that was specified in the dynamically generated provider
configuration file
Using above approach, you don't need to change existing code.
Move the call into a protected method and override it in the test. This allows you to return anything during the tests.
Services can usually be replaced at runtime.
If you are using OSGi you can replace the service implementation in a set up method annotated with @BeforeClass
and unregister the mocked implementation in an @AfterClass
method:
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();
}