I am creating a JUnit TestCase for a project which needs to load a configuration file during initialization.
This configuration file is inside the project in src/main/resources/config folder and during the build maven places it into /config folder, inside the JAR.
The initialization class, reads the file from there using this statement:
ClassLoader classloader = this.getClass().getClassLoader();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream("/config/config.xml")));
The problem I have is that when I deploy and execute this jar into the application server it works as expected, however, whenever I run it in a JUnit TestCase within Eclipse, the getResrouceAsStream method returns null.
Considering that the class is my.package.MyClassTest.java, and that it lives in src/test/java/my/package/MyClassTest.java, I already tried placing a copy of the config.xml file into the following folders without success:
- src/test/resources/config
- src/test/resources/my/package/config
- src/test/java/my/package/config
I know that similar questions have been asked many times here in StackOverflow, but all the responses I found refer to changing the way the file is loaded and, although changing the code may be an option, I would prefer to just find the right place for the file so I do not need to modify things which already work in the production environment.
So, where should I place this file to be able to use it in my JUnit test?
UPDATE
I just came up with the solution with a small change in the code: Instead of using the ClassLoader to get the resource, I directly used the class:
Class clazz = this.getClass();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/config/config.xml")));
And it reads the file successfully from src/test/resources/config/config.xml.
However, there's is something very weird here: The Class.getResourceAsStream method is:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
And if I debug it, I can clearly see that this getClassLoader0() returns exactly the same object (same id) than the previous call, this.getClass().getResourceAsStream() (which I maintained, just to compare the values)!!!
What's going on here?!
Why does calling the method directly not work, while inserting a new method call in between works?
Honestly, I'm really astonished in front of this.
BTW, I am using JUnit version 4.10. May it be tampering the getClassLoader call in some way?
Many thanks,
Carles
Replying as to your question
And if I debug it, I can clearly see that this getClassLoader0() returns exactly the same object (same id) than the previous call, this.getClass().getResourceAsStream() (which I maintained, just to compare the values)!!!
What's going on here?!
Why does calling the method directly not work, while inserting a new method call in between works?
The difference between calling
this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
and calling
this.getClass().getResourceAsStream("/config/config.xml");
Lies in the exact source that you were showing from Class
:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
But the problem is not with what getClassLoader0()
returns. It returns the same thing in both cases. The difference is actually in resolveName(name)
. This is a private method in the Class
class.
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
So you see, before actually calling the classLoader's getResourceAsStream()
, it actually removes the starting slash from the path.
In general, it will try to get the resource relative to this
when it doesn't have a slash, and pass it on to the classLoader if it does have a slash at the beginning.
The classLoader's getResourceAsStream()
method is actually intended to be used for relative paths (otherwise you would just use a FileInputStream
).
So when you used this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
, you were actually passing it the path with a slash in the beginning, which failed. When you used this.getClass().getResourceAsStream("/config/config.xml");
it was kind enough to remove it for you.