File access during test execution in OSGi projects

March 6, 2015 | 4 min Read

Unit tests should be small in scope and fast in execution. Shooting for tests like these during development, I am getting reliable and ready-to-use code units fairly quickly.

Once I have such a unit of code I integrate it into my system, and when my code unit meets the real world for the first time, it often fails. And I get a ton of new test cases.

Code unit, meet integration test

These days my code is often dealing with input from XML files. No matter how good our unit tests are, there is no way around having tests where a real-world XML is run through the code.

We are calling these tests integration level tests, and depending on the environment they are creating a whole problem space on their own. We have developed solution patterns for these problems and packed them into a helper. You’ll find the helper class further down, but first let’s dive into the problems it solves and how to address them.

The problem space

Handling test files in OSGi bundles is a nightmare consisting of file handles, resources, JRE and OSGi environments. Problems I regularly deal with include:

  • Pass the input as InputStream while running in a plain JRE environment.
  • Pass the input as InputStream while running in an OSGi environment.
  • Provide the input as a file handle while running in the IDE.
  • Provide the input as a path while running in the IDE.
  • Provide the input as a file handle while running on the build machine.
  • Provide the input as a path while running on the build machine.

Solutions

The problems come in different input types on different platforms. Let’s first discuss the simplest input type, the InputStream. A working solution is to use

getClass().getClassLoader().getResourceAsStream("myfileAtRoot.xml")

This method will look at the project root, and return the contents as an InputStream. Make sure to put the file to the project root after your build tool processed it, because what matters is where the file is located during runtime. And do not confuse this method call with

getClass().getResourceAsStream("myfileNextToTest.xml")

which will look in the package of the test class.

As long as you only need InputStream objects these methods will work quite well in both plain Java and OSGi context. All you need is a place for your test files where they’ll be at the same position in all environments. We use a source folder resources/ for this, and we don’t use subfolders below that one. Other solutions are possible but we found this works well in our project.

So let's look into file handles and paths. Since both rely on a file in the file system during runtime, they can be addressed with the same solution.

It is possible to find out the path of a file inside your project during runtime. But this is not a solution, because only on some runtime environments there actually will be a file on the file system. On the build system, the project is a jar file, and all you get is a URL pointing to something inside your jar file.

Our solution is a joint venture of InputStream input and the TemporaryFolder from JUnit. During setup we use the InputStream to read the resource and copy it to a directory that only exists during test execution.

One rule to solve it all

On github you can find the class FileResource. It is intended to be used as a JUnit Rule in your test class.

    @Rule
    public FileResource myFile = new FileResource(getClass().getClassLoader(), "helloworld.txt");

It takes 2 arguments:

  1. The class loader makes sure the resource is looked for in the project (aka OSGi bundle) where the class belongs to.
  2. The file name should be a file at the root of some source folder inside your project.

In your test, you have various ways to access the file. Here are what I consider the most important:

    @Test
    public void fileIsAccessible() {
        assertEquals("hello world", myFile.getContent());
        assertArrayEquals(myFile.getBytes(), fileResource.getContent().getBytes());
        assertNotNull(myFile.getFile());
        assertNotNull(myXMLFileResource.getContentAsW3CDocument());
        assertNotNull(myXMLFileResource.getContentAsJDomDocument());
    }

The FileResource does not handle nested directories, and you need to create a FileResource object for every file you want to use in your test.

By default you will also get an own folder for each FileResource. That folder will be cleaned up after the test run automatically (provided all Streams are closed). If you want to put multiple files in the same parent folder during runtime, a class ExternalFolder comes with the FileResource with intented use like this:

    private final ExternalFolder folder = new ExternalFolder();
    @Rule
    public FileResource helloEarth = new FileResource(folder, getClass().getClassLoader(), "helloearth.txt");
    @Rule
    public FileResource helloMars = new FileResource(folder, getClass().getClassLoader(), "hellomars.txt");
    @Test
    public void multipleFilesInSameFolder() throws Exception {
        assertEquals(helloEarth.getFile().getParentFile(), helloMars.getFile().getParentFile());
    }

Note that each instantiation of the class FileResource leads to copying a file on the hard disk, so this is probably not the best solution for your every-minute JUnit test. But if you restrict the use to integration-level tests this class can be quite handy.

How do you solve these problems in your project?