How to simulate static member variables

I have a ClassToTest class that has a dependency on ClassToMock.

  public class ClassToMock {

    private static final String MEMBER_1 = FileReader.readMemeber1 ();

    protected void someMethod () {
        ...
    }

 }

Unit test case for ClassToTest.

 public class ClassToTestTest {
    private ClassToMock _mock;

    @Before
    public void setUp () throws Exception {
       _mock = mock (ClassToMock.class)
    }

 }

When mock is called in the setUp () method, FileReader.readMemeber1 (); performed. Is there any way to avoid this? I think one way is to initialize MEMBER_1 inside the method. Any other alternatives?

Thanks!

+7
source share
2 answers

Your ClassToMock is closely related to FileReader , so you cannot test / mock it. Instead of using a tool to crack byte code so you can mock it. I would suggest you do some simple refactoring to break the addiction.

Step 1. Encapsulate global links

This technique is also presented in a wonderful book by Michael Persa: Effectively works with legacy code .

The name is largely explained. Instead of directly referencing a global variable, you encapsulate it inside the method.

In your case, ClassToMock can be reorganized as follows:

 public class ClassToMock { private static final String MEMBER_1 = FileReader.readMemeber1(); public String getMemberOne() { return MEMBER_1; } } 

, then you can easily use Mockito to make fun of getMemberOne() .

UPDATED The old step 1 cannot guarantee Mockito safely, if FileReader.readMemeber1() Mockito an exception, then the test will be overturned. Therefore, I propose adding one more step to get around it.

Step 1.5 add Setter and Lazy Getter

Since the problem FileReader.readMember1() will be called as soon as ClassToMock is loaded. We have to put it off. So we make a tape call to FileReader.readMember1() lazily and open the setter.

 public class ClassToMock { private static String MEMBER_1 = null; protected String getMemberOne() { if (MEMBER_1 == null) { MEMBER_1 = FileReader.readMemeber1(); } return MEMBER_1; } public void setMemberOne(String memberOne) { MEMBER_1 = memberOne; } } 

Now you can fake ClassToMock even without Mockito . However, this should not be the final state of your code, once you have the test ready, you should go to step 2.

Step 2. Dependency Injection

After you have prepared your test, you must reorganize it again. Now instead of reading MEMBER_1 yourself. This class should get MEMBER_1 from the outside world. You can use the installer or constructor to get it. Below is the code that uses setter.

 public class ClassToMock { private String memberOne; public void setMemberOne(String memberOne) { this.memberOne = memberOne; } public String getMemberOne() { return memberOne; } } 

These two-step refactoring is really easy to do, and you can do it even without checking. If the code is not so complicated, you can just do step 2. Then you can easily test ClassToTest


UPDATE 12/8: Reply to the comment

See my other answer in this question.

+9
source

UPDATE 12/8: Reply to the comment

Question: what if FileReader is something very simple, like Logging, to be there in every class. Do you suggest that I take the same approach?

It depends.

There is something you might think about before doing massive refactoring.

  • If I move FileReader outside, do I have a suitable class that can read from a file and provide the result for each individual class that they need?

  • Besides simplifying class testing, do I get any other benefits?

  • Do i have time

If any of the answers is NO, you better not do this.

However, we can still break the dependency between all classes and FileReader with minimal changes.

From your question and comment, I assume that your system uses FileReader as a global link to read material from the properties file, and then provide it to the rest of the system.

This technique is also featured in a great book by Michael Persa: Effectively Work with Legacy Code .

Step 1. Delegate the FileReader static methods to the instance.

Change

 public class FileReader { public static FileReader getMemberOne() { // codes that read file. } } 

For

 public class FileReader { private static FileReader singleton = new FileReader(); public static String getMemberOne() { return singleton.getMemberOne(); } public String getMemberOne() { // codes that read file. } } 

Thus, the static methods in FileReader no longer know how getMemberOne()

Step 2. Extract the interface from FileReader

 public interface AppProperties { String getMemberOne(); } public class FileReader implements AppProperties { private static AppProperties singleton = new FileReader(); public static String getMemberOne() { return singleton.getMemberOne(); } @Override public String getMemberOne() { // codes that read file. } } 

We retrieve the entire method in AppProperties , and the static instance in FileReader now uses AppProperties .

Step 3. Static Setter

 public class FileReader implements AppProperties { private static AppProperties singleton = new FileReader(); public static void setAppProperties(AppProperties prop) { singleton = prop; } ... ... } 

We opened the seam in FileReader. By doing this, we can set the base instance of the change in FileReader , and it will never notice.

Step 4. Cleaning

FileReader now has two functions. One of them is the read files and provides the result, the other is a global link for the system.

We can separate them and give them a good name. Here is the result:

 // This is the original FileReader, // now is a AppProperties subclass which read properties from file. public FileAppProperties implements AppProperties { // implementation. } // This is the class that provide static methods. public class GlobalAppProperties { private static AppProperties singleton = new FileAppProperties(); public static void setAppProperties(AppProperties prop) { singleton = prop; } public static String getMemberOne() { return singleton.getMemberOne(); } ... ... } 

END.

After that refactoring when you want to test. You can install mock AppProperties in GlobalAppProperties

I think this refactoring will be better if all you want to do is break the same global dependency in many classes.

+3
source

All Articles