Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

getResourceAsStream from JUnit

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

like image 828
Carles Sala Avatar asked Jul 18 '13 17:07

Carles Sala


Video Answer


1 Answers

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.

like image 156
RealSkeptic Avatar answered Oct 04 '22 15:10

RealSkeptic