2

I am using jmockit version 1.24 with junit5 where I am mocking a public method of a singleton class as shown below.

Here's my Test.java:

@Test
void myTest() {
    MockUp mySingletonMock = new MockUp<MySingleton>() {
        @Mock
        public FileHeaders getHeader(String localFilePath) {
            return new FileHeaders(checksum, "", "", new Date());
        }
    };

    // Some assert statements 

    mySingletonMock.tearDown();
}

And this is Singleton.java:

public class MySingleton {
    private static MySingleton instance = new MySingleton();

    private MySingleton(){
        // Some initialization
    }

    public static MySingleton getInstance(){
        return instance;
    }

    public FileHeaders getHeader(String localFilePath) {
        ...
    }
}

I am facing a problem with the above approach where all tests that execute after myTest completes execution fail as they still see the mocked getHeader method instead of the original one in the MySingleton class (I have verified that this is indeed the case using debug statements).

How to prevent this mocked version of getHeader method being seen in other tests? (preferably without changing the version of jmockit).

The weird part of all this is that the tests run without any issue locally on my system using maven. But fail when run on teamcity.

Any help is appreciated. Thank you.

Edit: Things I have tried:

  • I have tried adding the $clinit() method to the MockUp. But no luck.

  • I have reset the singleton instance to a new instance through reflection at the end of my test as shown below. This did not solve the problem either.

void resetMySingletonInstance() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
    Constructor<?>[] constructors = MySingleton.class.getDeclaredConstructors();
    Constructor theConstructor = constructors[0];
    theConstructor.setAccessible(true);

    // Verified that this gives a new instance
    MySingleton instanceMySingleton = (MySingleton) theConstructor.newInstance();
    Field ourInstanceField = MySingleton.class.getDeclaredField("ourInstance");
    ourInstanceField.setAccessible(true);
    ourInstanceField.set(null, instanceMySingleton);
}
Srinidhi Shankar
  • 311
  • 5
  • 15

1 Answers1

0

First I would not initialize and tear down the mocks in the test methods directly, but instead use setup() and tearDown() methods (@Before and @After annotations).

private List<MockUp<?>> mockUps = new ArrayList<>();
    
public void addMockUp(MockUp<?> mockUp) {
    mockUps.add(mockUp);
}

@Before
public void setup() throws Exception {
    addMockUp(new MockUp<MySingleton>() {
        @Mock
        public FileHeaders getHeader(String localFilePath) {
            return new FileHeaders(checksum, "", "", new Date());
        }
    });
    
    // other mocks ?
}

@Test
void myTest() {     
    // Some assert statements on MySingleton.getInstance()
}

@After
public void tearDown() {        
    mockUps.stream().forEach(mock->mock.tearDown());
}

But the issue in your test is that MySingleton is initialized with a mock object. If all your tests are executed in the same VM, this mock object will be reused for subsequent tests. I'm not sure why you don't have this issue locally but only on your build server, maybe configuration of VM forking : https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html

To fix this, you need to find a way to reinitialise the singleton after each test. For example, one solution would be to change MySingleton to be able to reset the instance after the test. Provided MySingleton is not accessed concurrently, you could use lazy loading for example and reset it in the tearDown method.

public class MySingleton { private static MySingleton instance;

private MySingleton(){
    // Some initialization
}

public static MySingleton getInstance(){
    if(instance == null) {
        instance = new MySingleton();
    }
    return instance;
}

public static void reset() {
    instance = null;
}

public FileHeaders getHeader(String localFilePath) {
    ...
}

}

You have also another option if you can't change production code to reset the field by reflection: Set Value of Private Static Field

st6ph
  • 76
  • 1
  • 5
  • Thanks for taking time to reply. 1. The reason I have the MockUp inside the test is that I want the mock to affect only one test. 2. I had tried resetting the singleton instance through reflection at the end of my test, but it didn't fix the problem. – Srinidhi Shankar Aug 12 '20 at 15:00