Given synchronized and Lombok's @Synchronized, the latter causes a NullPointerException when mocking a method under test. Given
public class Problem
{
public Problem()
{
// Expensive initialization,
// so use Mock, not Spy
}
public synchronized String a()
{
return "a";
}
@Synchronized // <-- Causes NPE during tests, literally, here
public String b()
{
return "b";
}
}
and the Jupiter test class
class ProblemTest
{
@Mock
private Problem subject;
@BeforeEach
void setup()
{
initMocks(this);
// There is more mocking. Please don't let the simplicity
// of this example throw you off.
doCallRealMethod().when( subject ).a();
doCallRealMethod().when( subject ).b();
// This is a hack, but works. Can we rely on this?
// ReflectionTestUtils.setField( subject, "$lock", new Object[0] );
}
@Test
void a()
{
// Succeeds
assertEquals( "a", subject.a() );
}
@Test
void b()
{
// NullPointerException during tests
assertEquals( "b", subject.b() );
}
}
Lombok adds something like the following:
private final Object $lock = new Object[0]; // We can't rely on this name
...
public String b()
{
synchronized($lock)
{
return "b";
}
}
How to mock a method that is decorated with Lombok's default @Synchronized annotation?
Here is the stack trace, though it is unhelpful. I suspect Lombok adds a field as in my example above, and of course that is not injected into the mock, so voilĂ , NPE.
java.lang.NullPointerException
at com.ericdraken.Problem.b(Problem.java:16) // <-- @Synchronized keyword
at com.ericdraken.ProblemTest.b(ProblemTest.java:43) // <-- assertEquals( "b", subject.b() );
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
... [snip] ...
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)