File access during test execution in OSGi projects
March 6, 2015 | 4 min ReadUnit 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.
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:
- 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?