考虑到 synchronized
和 Lombok 的 @Synchronized
,后者在模拟被测方法时会导致 NullPointerException
。鉴于
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";
}
}
和 Jupiter 测试类
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 添加了如下内容:
private final Object $lock = new Object[0]; // We can't rely on this name
...
public String b()
{
synchronized($lock)
{
return "b";
}
}
如何模拟用 Lombok 的 default @Synchronized
注释修饰的方法?
这是堆栈跟踪,尽管它没有帮助。我怀疑 Lombok 添加了一个字段,如上面的示例所示,当然,它没有注入(inject)到模拟中,所以瞧,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)
最佳答案
这不是 Lombok 的问题,以下也失败。
@ExtendWith({MockitoExtension.class})
@MockitoSettings(strictness = Strictness.LENIENT)
public class ProblemTest {
@Mock
private Problem subject;
@BeforeEach
void setup()
{
doCallRealMethod().when( subject ).c();
}
@Test
void c()
{
// NullPointerException during tests
assertEquals( "c", subject.c() );
}
}
class Problem
{
private final Map<String,String> c = new HashMap<>(){{put("c","c");}};
public String c(){
return c.get("c");
}
}
准确地说,您并不是真正在 mock 问题
,而是通过doCallRealMethod
部分 mock ,因此出现了问题。
这也在 Mockito 的 documentation 中被提及。 ,
Mockito.spy() is a recommended way of creating partial mocks. The reason is it guarantees real methods are called against correctly constructed object because you're responsible for constructing the object passed to spy() method.
doCallRealMethod()
在模拟上调用,但不能保证以应有的方式创建对象。
所以回答你的问题,是的,这就是你创建模拟的方式,但是无论Lombok如何,doCallRealMethod
始终是一场赌博。
如果您确实想调用实际方法,可以使用 spy
。
@Test
void c() {
Problem spyProblem = Mockito.spy(new Problem());
assertEquals("c", spyProblem.c());
verify(spyProblem, Mockito.times(1)).c();
}
关于java - Lombok @Synchronized 与 Mockito 抛出 NPE,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63627361/