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:
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.
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.