06 Mar 2015
File access during test execution in OSGi projects
Comments
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
Here 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:
- The class loader makes sure the resource is looked for in the project (aka OSGi bundle) where the class belongs to.
- 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?