javaunit-testingmockingmockitoobject-construction

Mocking object construction in unit tests


I would like to write unit tests for a class which creates an instance of a problematic class, and I am working under the following constraints:

  1. My class calls some third-party library initialization code.
  2. The third-party code creates an instance of the problematic class. This is fine, and I should not replace this instance with a mock, in order not to alter the behavior of the third-party library.
  3. My class creates an instance of the problematic class. I should mock the object construction; for example, I would make the problematic object constructor throw an exception.

I am using Mockito 5.5.0 and JUnit Jupiter 5.9.3.

Here is a fairly minimal example:

class Problematic {

    public Problematic(String description) {
        System.out.println("Problematic object constructed: " + description);
    }
}

class ThirdPartyLibrary {

    public static void thirdPartyCode() {
        new Problematic("this is fine");
    }
}

class MyClass {

    public static void myCode() {
        ThirdPartyLibrary.thirdPartyCode();
        new Problematic("this needs to be mocked");
    }
}

class MockProblematicObjectConstructionTest {

    @Test
    void test() {
        try (MockedConstruction mockedProblematic = mockConstruction(Problematic.class, (mock, context) -> {
            // Now what?
            System.out.println("Context arguments: " + context.arguments());
        })) {
            MyClass.myCode();
        }
    }
}

How can I intercept and/or mock Problematic's constructor, but only when it has been called from MyClass?

This is part of an incremental refactoring of legacy code, and I would like to write "pin-down tests" before starting the actual refactoring.


Solution

  • Some refactorings are less likely than others to cause problems. In this case I would suggest to refactor the class so that the construction of your problematic class can be controlled.

    class MyClass {
        // visible for testing
        static Function<String, Problematic> problematicFactory = Problematic::new;
    
        public static void myCode() {
            ThirdPartyLibrary.thirdPartyCode();
            problematicFactory.apply("this needs to be mocked");
        }
    }
    

    It should be trivial to verify that this does not change the behavior of your class (you could start your application once and verify that it still works – or you trust that such a minimal change does not cause problems).

    Then in your test, you can change the construction to something else:

    class MockProblematicObjectConstructionTest {
        @Test
        void test() {
            MyClass.problematicFactory = arg => {
              throw new RuntimeException("whatever");
            };
    
            MyClass.myCode();
        }
    }
    

    Eventually, you can then transition your class to use dependency injection via its constructor and hide/encapsulate the field again.