Reputation: 603
I have a method that uses ServiceLoader
to load services using resources.
public List<String> getContextData(int Id)
{
List<String> list = new ArrayList<String>();
ServiceLoader<ContextPlugin> serviceLoader = ServiceLoader.load(ContextPlugin.class);
for (Iterator<ContextPlugin> iterator = serviceLoader.iterator(); iterator.hasNext();)
{
list .addAll(iterator.next().getContextData(Id));
}
return list;
}
How should I unit test above method using Junit?
Upvotes: 15
Views: 5860
Reputation: 5251
If you have only one configuration to test, you can use a test/resources/META-INF/services/mypackage.MyInterface
file, see @SubOptimal answer. However, if you need to test more than one configuration, this approach does not work.
To test more than configuration, I'm using a custom classloader which overrides the META-INF/services/mypackage.MyInterface
. Basically, you use this classloader as parameter in ServiceLoader.load(MyInterface.class, customClassLoader)
.
The advantage of this approach is that it does not rely on mocking libraries such as Mockito or Powermock.
To create a classloader without META-INF/services/mypackage.MyInterface
file:
new ServiceLoaderTestClassLoader(MyInterface.class)
To create a classloader with a META-INF/services/mypackage.MyInterface
file containing two lines with classes SubClassOfMyInterface
and OtherSubClassOfMyInterface
:
new ServiceLoaderTestClassLoader(MyInterface.class, SubClassOfMyInterface.class, OtherSubClassOfMyInterface.class)
This is the custom classloader source code:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Collections;
import java.util.Enumeration;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Testing classloader for ServiceLoader.
* This classloader overrides the META-INF/services/<interface> file with a custom definition.
*/
public class ServiceLoaderTestClassLoader extends URLClassLoader {
Class<?> metaInfInterface;
Class<?>[] implementingClasses;
/**
* Constructs a classloader which has no META-INF/services/<metaInfInterface>.
*
* @param metaInfInterface ServiceLoader interface
*/
public ServiceLoaderTestClassLoader(Class<?> metaInfInterface) {
this(metaInfInterface, (Class<?>[]) null);
}
/**
* Constructs a fake META-INF/services/<metaInfInterface> file which contains the provided array of classes.
* When the implementingClasses array is null, the META-INF file will not be constructed.
* The classes from implementingClasses are not required to implement the metaInfInterface.
*
* @param metaInfInterface ServiceLoader interface
* @param implementingClasses potential subclasses of the ServiceLoader metaInfInterface
*/
public ServiceLoaderTestClassLoader(Class<?> metaInfInterface, Class<?>... implementingClasses) {
super(new URL[0], metaInfInterface.getClassLoader());
if (!metaInfInterface.isInterface()) {
throw new IllegalArgumentException("the META-INF service " + metaInfInterface + " should be an interface");
}
this.metaInfInterface = metaInfInterface;
this.implementingClasses = implementingClasses;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("META-INF/services/" + metaInfInterface.getName())) {
if (implementingClasses == null) {
return Collections.emptyEnumeration();
}
URL url = new URL("foo", "bar", 99, "/foobar", new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) {
return new URLConnection(u) {
@Override
public void connect() {
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(Stream.of(implementingClasses)
.map(Class::getName)
.collect(Collectors.joining("\n"))
.getBytes());
}
};
}
});
return new Enumeration<>() {
boolean hasNext = true;
@Override
public boolean hasMoreElements() {
return hasNext;
}
@Override
public URL nextElement() {
hasNext = false;
return url;
}
};
}
return super.getResources(name);
}
}
Upvotes: 2
Reputation: 22973
You need to copy the "provider-configuration file" into your test class directory.
assuming your test class files are located at
test/classes/
you need to copy the "provider-configuration file" to
test/classes/META-INF/services/your.package.ContextPlugin
How to copy the files depend on your build tool (e.g. maven, gradle, ant, ...)
As example for maven you should store them in the test resources folder.
src/test/resources/META-INF/services/your.package.ContextPlugin
Upvotes: 8