2

I have my class as

    public class MappingLoader
    {
     private static final String filepath = "/tmp/mapping.properties" // unix path of production system
     private static Map<String,String> mapping = new HashMap<String,String>()

     static
     {
        loadMappingFile()
     }


@VisibleForTesting     
static void loadMappingFile()
    {
      //reading properties files here

     final Properties prop = new Properties();
        try (final InputStream input = Files.newInputStream(Paths.get(filepath)))
        {
            prop.load(input);
        }
        catch (final Exception e)
        {
            ...
            ...
            throw new RuntimeException(e);
        }

               //now load "mapping" from properties file
              ....
                  ....
        }
    }

For testing, I need to change the value of string variable "filepath" such that it should take development system path(say c:\project\target\mapping.properties)

I have tried powermocks in junits, but it always throws exception and terminates.

annotations at class level:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

and in test case:

Whitebox.setInternalState(Mappingloader.class, "filepath", testfilepath);
Mappingloader.loadMappingFile();

I also tried to change this via reflection as given in(Change private static final field using Java reflection) but it always throws the FileNotFoundException for "filepath" and does not takes the changed path "testFilePath"

Is there any way i can change this variable such that it does not throw FileNotFoundException without any modification in source code?

If I remove "throw new RuntimeException(e);" in source code, powermocks works for me. but i want to achieve this without modifying source code, either via powermock, reflection api.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
akhi
  • 664
  • 2
  • 10
  • 23

3 Answers3

3

Well, you can try your luck with Powermock; and that should work (maybe if you spend some more hours reading its documentation and making experiments); but honestly: your problem is not testing. Your problem is that you created untestable code. And now you are trying to use the big powermock hammer to "fix" what is your broken design.

You see, when using static methods and constants; people think they "save" on performance (which is true; but to a very small degree; which probably doesn't matter for 99.999% of all applications); but they keep forgetting that using static leads to direct coupling of different functionalities. static is an abnormality in good OO design; and should be used with great care.

So, in your case, you could replace the whole thing with something along these lines:

interface MappingLoader {
  Map<String, String> loadMappingsFrom(String fileName);
}

class MappingLoaderImpl implements MappingLoader { 
  ...

and you see, all of a sudden you are only dealing with "real" interfaces and classes; and non-static methods; and surprise: now you can fully unit-test the whole thing; and most likely, you don't even need a mocking framework. You just create a temp file with mappings somewhere; and then you make sure that your impl class gives you the mappings from that file. Zero mocking; zero testing of internal implementation details; just a few asserts.

And another advantage on top: all your client code that should only be using the MappingLoader interface can also be tested. Because ordinary frameworks like EasyMock or Mockito will allow you to mock out instances of that interface ... because: no static calls any more!

That is how you change the value of private final static fields - by not using them!

(and if I made you curious: watch this to learn how to write testable code from the beginning)

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    Excellent response! I completely agree on how to design testable code. And even more: Once you have an instantiable, reusable and testable abstraction to read any property file, there is not objection to put a static facade to read a specific file. (If the abstraction has been fully tested, there is no need to test the facade.) – Little Santi Aug 13 '16 at 19:26
  • @GhostCat, everything you said are good thinks and how it should be. But you didn't say, how it could be achieved when a developer already has such code and maybe it's a part of library which cannot be modified. Of course a developer should follow all these principles, avoid using static and so on , so on and, but when a project is started from scratch. If code is not covered by test - make change, it's not a good idea. – Artur Zagretdinov Aug 14 '16 at 08:06
  • I completely agree with @GhostCat on design principles and adding here my vote for the knowledge that is shared. – akhi Aug 14 '16 at 08:28
  • @ArthurZagretdinov Sure, there are situations where you don't have the choice of reworking code. But then: if it is not **your** library; do you really have to test the functionality of the **library**? You see, if you think "changing that source code is not possible"; what is point of testing it? Finding bugs ... which you can't fix; because "changing that source code is not possible?!" Of course, I am talking "in extremes" here; but far too often people simply do a bad design; and then they think powermock/ito is the answer to that. And, no, it is simply not that easy. – GhostCat Aug 14 '16 at 08:28
  • @GhostCat, Passing file name may not fit in all desired cases. For example what if the file path is private and file has to be loaded first and only once during during the class loading? – akhi Aug 14 '16 at 09:38
  • @akhi I would say: such rare error cases; again are not what 99.999% of all applications really need. And even then it would be perfectly possible to build a solution that does not need statics.I am mainly saying: be aware of A) the consequences (price tag) of using static B) the fact, that in most cases, designs can be improved to simply not need static any more. – GhostCat Aug 14 '16 at 09:44
  • I disagree. The real problem from the code in the question is that it relies on a hard-coded and inaccessible constant. A good and simple solution would be to make this value configurable, for example by reading it from a system property (ie, `private static final String filepath = System.getProperty("mappingsFile", "/tmp/mapping.properties");`). Then the property can be set in a test run to point to a test file; no mocking needed, indeed. Introducing a `MappingInterface` would be unnecessarily complicating things. – Rogério Aug 18 '16 at 19:47
1

While I completely agree with @GhostCat's response, I understand you are looking for a solution not involving to change the source code. Have you thought of changing the contents of /tmp/mapping.properties before the test runs (and restore them later)?

Little Santi
  • 8,563
  • 2
  • 18
  • 46
0

static final String fields or any final static primitives fields cannot be modified in runtime. If speed honestly you can modify these fields, but your change will not affect code which uses these fields, because during compile time reference is replaced by value.

My suggestion: use static mock to Files.newInputStream()call and then return ByteArrayInputStream with expected data. In this case you will avoid fragile disk IO operation which could affect your test stability.

Artur Zagretdinov
  • 2,034
  • 13
  • 22
  • Disk IO (from a local filesystem as in this case, at least) is not a fragile operation, and it should not be mocked. Whenever we run a Java program, lots of disk IO happen anyway. – Rogério Aug 18 '16 at 19:52