Search code examples
unit-testingmockitowhite-box-testing

Undoing Whitebox.setInternalState in a @AfterMethod without setting original state


I have a class which I cannot easily use a dependency injection to mock due to an obligatory implementation of an interface - in a nutshell, for that reason I will be using Whitebox and my concern here is not related to the design, instead it is just to figure out how to properly "tearDown" the behavior caused by Whitebox. Bear with me for a second, I will give you more details - this is the main dummy class:

public class Dummy implements MandatoryInterface {
    private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public Object convertArgumentToJson(Object arg) {
        if (arg != null) {
            try {
                return mapper.writeValueAsString(arg);
            } catch (IOException e) {
                // doSomething();
                logger.error("Error tracking request", e);
            }
        }
        return null;
    }

}

Supposing that I want to cover what happens if an exception occurs here, the only way that I see is to use a Whitebox.setInternalState. Here the test:

public class DummyTest {
    private Dummy dummy = new Dummy();

    @Test
    public void testA() throws IOException {

        final ObjectMapper mock = Mockito.mock(ObjectMapper.class);
        Whitebox.setInternalState(dummy, "mapper", mock);


        Mockito.when(mock.writeValueAsString(Mockito.any()))
                 .thenThrow(new IOException());

        Assert.assertNull(dummy.convertArgumentToJson("testA"));

    }

    @Test
    public void testB() {
        Assert.assertNotNull(dummy.convertArgumentToJson("testB"));
    }

}

As you can see, I cannot define the mapper within the Dummy class as static, because of the Whitebox (it wouldn't work). Having said that, after the execution of testA() we have the mapper being mocked:

enter image description here

The problem is: when executing the testB I don't want mock anymore - it should be the old instanced ObjectMapper initially included in Dummy. But what appears:

enter image description here

Now, my question:

What is the proper way to undo the

 Whitebox.setInternalState(dummy, "mapper", mock);

P.S.: I have considered using a tearDown() like this:

@AfterMethod
public void tearDown(){
    Whitebox.setInternalState(dummy, "mapper", originalState);
}

However, in this scenario, my pitest (mutation test) would consider that I'm not covering the initialization of ObjectMapper, so: is there a way just to undo the Whitebox for the rest of the tests without setting manually the old one?

Sorry for the long description and thanks in advance.

Regards,


Solution

  • Sorry guys, I managed to have it.

    Just in case someone else could face the same question, the answer was easier than I supposed.

    private static final String MAPPER_DESC = "mapper";
    private ObjectMapper originalMapper;
    
    @BeforeMethod
    public void init() {
        MockitoAnnotations.initMocks(this);
         originalMapper = (ObjectMapper) Whitebox.getInternalState(converter, MAPPER_DESC);
    }
    
    @AfterMethod
    public void tearDown() {
        Whitebox.setInternalState(converter, MAPPER_DESC, originalMapper);
    }
    

    Then testA and testB can keep the same code. And the mutation test will still have the ObjectMapper attribute declaration covered as shown in the image: enter image description here