java - Lombok @Synchronized 与 Mockito 抛出 NPE

标签 java mockito junit5 synchronized lombok

考虑到 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/

相关文章:

java - 对 Domino 对象使用回收会导致 Bean 不返回数据

scala - Mockito:对重载定义的引用不明确

java - 使用@RunWith Annotation 和 powerMock 时的问题

java - Mockito 验证对模拟对象的最后一次调用

spring - 将 Spring bean 注入(inject) JUnit Jupiter 中的 ParameterResolver

java - 使用 Java Swings 和 mysql 自动填充表单

java - 如何维护多个异常的单个日志文件

java - 使用 doReturn 为 Spring Service 实现 JUnit 测试

java - 如何向 JPanel 添加和删除标签?

java - 如何断言已将适当的值分配给私有(private)字段?