getResourceAsStream from JUnit
Asked Answered
R

3

14

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

Rosenblast answered 18/7, 2013 at 17:16 Comment(3)
Is src/test/resources/ a source folder in Eclipse?Deepfreeze
@SotiriosDelimanolis if you post this as an answer, I will vote it up.Avram
@SotiriosDelimanolis: Yes, it is. Indeed, when I looked at it I realized that Eclipse takes it by default for maven projects.Rosenblast
T
18

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.

Turban answered 29/10, 2014 at 19:23 Comment(1)
I'm afraid that I cannot currently test it (I would like to try manually removing the first slash from the route and see if it works the same way), but sounds convincing. Thanks, and accepted.Rosenblast
R
2

getResourceAsStream function from ClassLoader object will not remove slash prepended with your search string since the search will be done relative to the classpath. i.e searches the resource where search path used to load classes.

Say for example if your class yourpackage/Test.class, which is located under /a/b/c/d/yourpackage/Test.class, loaded by system classloader(i.e default classloader) and your classpath must point to /a/b/c/d in order to load the class. Search will be done on to this path.

getResourceAsStream function from Class Object does removes the slash prepended with your search string since the search will be done relative to the class where it resides. i.e searches the resource where your class being loaded from.

Say for example if yourpackage/Test.class loaded from /a/b/c/d/yourpackage/Test.class then the resource path will be /a/b/c/d/yourpackage/config/config.xml

You can test this using following code snipet since both getResource and getResourceAsStream used to follow the same algorithm for search.

System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));
Regime answered 3/11, 2014 at 12:29 Comment(0)
R
1

I'm not sure about this, but you could try putting your resources folder in the src/main tree instead of the src/test tree. In some configurations, at least, eclipse copies 'resource' files from src to classes, but not from test to classes. It's worth a shot...

Rotator answered 18/7, 2013 at 18:30 Comment(1)
Both resources folders exist in src/main and src/test, containing the same files...but it does not work.Rosenblast

© 2022 - 2024 — McMap. All rights reserved.