0

I'm having trouble changing the value of a field for testing purposes.

Despite the issues around singletons I want to use it for my log files manager. I actually gave it a thought if I should rework my design but was happily approved that my decision was probably OK.

I want to test it though. Especially if the code is run on windows and linux and maybe somewhere else I can't think of right now.

The idea is to create a testing directory in the current directory called "testlogs", delete it and play with it. So I can be sure I have write privileges at least in the current directory or I can inform the user that no log files will be written or I can prompt him to select another place etc.

In the following code I keep getting my "logs" directory. I would like to be able to spare the real "logs" (if they exist).

Why can't I change the value of dirname ?

package com.home.app.logger.LogsMngr;
public class LogsMngr {
    private static class Holder {
        public static String dirname = "logs";
        public static final LogsMngr INSTANCE = new LogsMngr(dirname);
    }

    private LogsMngr(String dirname){
         createDirectory(dirname);
    }

    private createDirectory(String dirname) {
         // logic to create a directory on the filesystem
    }

    public static LogsMngr getInstance() {
        return Holder.INSTANCE;
    }
}


@RunWith(SeparateClassLoaderRunner.class)
public class Test {

    LogsMngr lmngr;

    @Test
    public void test() {
        try {
            // Remove existing directory
            TUtils.rmfDirectory("testlogs");

            // Set directory name
            String className = "com.home.app.logger.LogsMngr$Holder";
            Class[] memberClasses = LogsMngr.class.getDeclaredClasses();

            Field dirnamefield = null;

            for(int i=0; i<memberClasses.length; i++) {

                if(memberClasses[i].getName().equals(className)) {

                    dirnamefield = memberClasses[i].getField("dirname");
                }
            }
            //TODO catch NullPointerException because of dirname?

            // To prevent from IllegalAccessException:
            dirnamefield.setAccessible(true);

            // Set test directory
            dirnamefield.set(null, "testlogs");

            // Create
            lmngr = LogsMngr.getInstance();
        }
        catch(Exception e) {

            e.printStackTrace();
        }
    }
}

References:

SeparateClassLoaderRunner.class

Java language docs

What's the proper way to test a class with private methods using JUnit?

Can I discover a Java class' declared inner classes using reflection?

The_solution_of_Bill_Pugh

Solution:

package com.home.app.logger.LogsMngr;
public class LogsMngr {
    private static String dirname = "logs"; <-- moved out of Holder

    private static class Holder {
        public static final LogsMngr INSTANCE = new LogsMngr(dirname);
    }

    private LogsMngr(String dirname){
         createDirectory(dirname);
    }

    private createDirectory(String dirname) {
         // logic to create a directory on the filesystem
    }

    public static LogsMngr getInstance() {
        return Holder.INSTANCE;
    }
}


@RunWith(SeparateClassLoaderRunner.class)
public class Test {

    LogsMngr lmngr;

    @Test
    public void test() {
        try {
            // Remove existing directory
            TUtils.rmfDirectory("testlogs");

            Field dirnamefield = LogsMngr.class.getDeclaredField("dirname");

            // To prevent from IllegalAccessException:
            dirnamefield.setAccessible(true);

            // Set test directory
            dirnamefield.set(null, "testlogs");

            // Create
            lmngr = LogsMngr.getInstance();
        }
        catch(Exception e) {

            e.printStackTrace();
        }
    }
}
Community
  • 1
  • 1
panny
  • 2,186
  • 4
  • 23
  • 26

1 Answers1

1

This line:

Class[] memberClasses = LogsMngr.class.getDeclaredClasses();

will cause LogsMngr.Holder to be initialized, causing the INSTANCE field to also be initialized, so changing dirname at any time after that will have no effect.

To do what you're trying to accomplish, you'd have to be able to set the dirname field without first initializing the class containing it, which I believe is impossible in Java, though I'd be interested to be corrected on that point. However, I believe you can get the effect you're looking for by simply moving dirname out of Holder and into LogsMngr. Then simply set LogsMngr.dirname to whatever value you want before using LogsMngr.Holder for anything.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199