6

This surely is a common problem. I have a properties file like my-settings.properties which is read by an application class. When I write a test class, it needs to test different scenarios of things that could be present in my-settings.properties in order to ensure maximum code coverage (e.g. empty properties file, basic properties file etc). But I can only have one my-settings.properties in my src/test/resources.

What would be really great is if there was just some annotation

@MockFileOnClassPath(use = "my-settings-basic.properties", insteadOf = "my-settings.properties")

Then I could just have multiple my-settings-XXX.properties files in my /src/test/resources and just annotated the correct one on each test method. But I can't find anything like this. I'm using JUnit 4.12.

I can think of a couple of crude solutions:

  1. Before each test, find the file on the file system, copy it using filesystem I/O, then delete it again after the test. But this is clumsy and involves a lot of redundancy. Not to mention I'm not even sure whether the classpath directory will be writable.
  2. Use a mocking framework to mock getResource. No idea how I would even do that, especially as there are a million different ways to get the file (this.getClass().getResourceAsStream(...), MyClass.class.getResourceAsStream(...), ClassLoader.getSystemClassLoader().getResourceAsStream(...) etc.)

I just think this must be a common problem and maybe there is already a solution in JUnit, Mockito, PowerMock, EasyMock or something like that?

EDIT: Someone has specified that this question is a duplicate of Specifying a custom log4j.properties file for all of JUnit tests run from Eclipse but it isn't. That question is about wanting to have a different properties file between the main and test invocations. For me I want to have a different properties file between a test invocation and another test invocation.

Community
  • 1
  • 1
Adam Burley
  • 5,551
  • 4
  • 51
  • 72
  • 1
    This is a really good question. Are you fetching a `Properties` object frequently enough to be able to stub that out? Even though there are a million ways to get properties, does your code access the properties file in a uniform way, which would eliminate some of your test vectors that you'd have to mock? Or is it a black box, and you don't know? – Shotgun Ninja Sep 28 '15 at 15:10
  • 1
    My code does currently fetch the properties in a uniform way, but I don't want my test to fail in future just because someone changed the way in which a properties file was fetched. Not sure what you mean by "stub that out". Do you mean mock the constructor and `load` method of the `Properties` class to do nothing, then mock the `getProperty` method to only return properties from a map I create in my test class? That is quite a good idea...although it wouldn't help to verify that I'm passing the correct filename to `getResourceAsStream` (etc.) – Adam Burley Sep 28 '15 at 15:16
  • Yes, that's what I'm proposing, and you're right, it isn't future-proof. I feel that verifying the correct file name should be a different test... – Shotgun Ninja Sep 28 '15 at 15:23
  • @ShotgunNinja yes, it should be a different test, but how to do that? If I'm essentially ignoring the call to `getResourceAsStream` and such, by mocking the `Properties` class itself. – Adam Burley Sep 28 '15 at 15:26
  • @Cassian I'm struggling to understand your comment. Java properties only accepts one value per key in a properties file, and so does my application. I don't think I am "testing if a variable [is passed] from different files", although I don't really understand what that means either. – Adam Burley Sep 28 '15 at 15:28
  • possible duplicate of [Specifying a custom log4j.properties file for all of JUnit tests run from Eclipse](http://stackoverflow.com/questions/24231773/specifying-a-custom-log4j-properties-file-for-all-of-junit-tests-run-from-eclips) – Raedwald Sep 30 '15 at 11:58

2 Answers2

3

I find that whenever dealing with files, it's best to introduce the concept of a Resource.

eg:

public interface Resource {
    String getName();
    InputStream getStream();
}

Then you can pass the resource in via dependency injection:

public class MyService {
    private final Properties properties;

    public class MyService(Resource propFile) {
        this.properties = new Properties();
        this.properties.load(propFile.getStream());
    }

    ...
}

Then, in your production code you can use a ClasspathResource or maybe a FileResource or URLResource etc but in your tests you could have a StringResource etc.

Note, if you use spring you already have an implenentation of this concept. More details here

lance-java
  • 25,497
  • 4
  • 59
  • 101
  • The consumers of my class shouldn't need to know where the properties file is located. Of course, I could create a nullary constructor which just passes `DEFAULT_RESOURCE` to the single-argument constructor, but then my consumer is calling a different method from my test class. – Adam Burley Sep 28 '15 at 15:24
  • That's what I'd do, it's common to add methods to make testing easier... I'd make the one arg constructor protected rather than public since it's only for testing – lance-java Sep 28 '15 at 15:28
  • Oh, you mean package-protected? This may be the best idea of the solutions available. I still feel quite surprised there isn't an off-the-shelf solution to this problem though! – Adam Burley Sep 28 '15 at 15:50
  • Not package protected... just protected. Dependency injection is the best solution... anything else would invole a different classloader for each test file which is incredibly messy!!! – lance-java Sep 28 '15 at 16:00
  • Not sure how it could be accessed if it was just protected rather than package-protected. My test class doesn't extend from my class-under-test. Therefore I wouldn't expect it to be able to access protected (as opposed to package-protected) class members. – Adam Burley Sep 29 '15 at 11:37
  • Your test case should be in the same package as the service under test so it will be able to access any `protected` methods. Obviously it will also be able to access package protected methods too... my preference is for protected (in fact I don't think I've ever used package protected (aka friendly) in my entire career). – lance-java Sep 29 '15 at 14:48
  • Wow, I never knew that protected (as opposed to package-protected) methods can be accessed from within the same package!! – Adam Burley Sep 29 '15 at 15:22
2

You can change your Service class to accept the name of the resource file, then then use that name to load the resource.

public class MyService {

 public MyService(String resourceFileName){
   //and load it into Properties  getResourceAsStream(resourceFileName);
 }
}
Sajan Chandran
  • 11,287
  • 3
  • 29
  • 38