微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

龙目岛@与Mockito同步抛出NPE 简介解决方案1-场注入解决方案2-声明一个锁,然后进行现场注入

如何解决龙目岛@与Mockito同步抛出NPE 简介解决方案1-场注入解决方案2-声明一个锁,然后进行现场注入

给出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";
    }
}

和木星测试班

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() );
    }
}

龙目岛添加如下内容

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在上面的示例中添加一个字段,当然,该字段没有注入到模拟中,因此是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的问题,以下操作也会失败。

BehaviorSubject

确切地说,您并不是在嘲笑@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"); } } ,而是通过Problem 部分嘲笑,因此是问题所在。

这在Mockito的documentation中也被提及,

Mockito.spy()是创建部分模拟的推荐方法。原因是它确保针对正确构造的对象调用真实方法,因为您负责构造传递给spy()方法的对象。

doCallRealMethod在模拟中调用,不能保证以预期的方式创建对象。

所以要回答您的问题,是的,这就是创建模拟的方式,但是doCallRealMethod()始终是赌博,与Lombok无关。

如果您确实要调用实际方法,则可以使用doCallRealMethod

spy
,

简介

Lombok项目在方法上具有@Synchronized注释,用于隐藏基础和自动生成的私人锁,而synchronized则对this进行锁。

在使用Mockito模拟(不是间谍,因为在某些情况下我们不希望实例化完整的对象)时,不会初始化字段。这也意味着自动生成的“锁定”字段为空,这会导致NPE。

解决方案1-场注入

看看龙目岛source code,我们看到龙目岛使用以下锁名:

private static final String INSTANCE_LOCK_NAME = "$lock";
private static final String STATIC_LOCK_NAME = "$LOCK";

除非龙目岛(Lombok)将来突然改变,否则这意味着即使感觉像“黑客”,我们也可以进行现场注入:

@BeforeEach
void setup()
{
    initMocks(this);
    ...
    ReflectionTestUtils.setField( subject,"$lock",new Object[0] );
}

解决方案2-声明一个锁,然后进行现场注入

该问题询问的是@Synchronized,而不是@Synchronized("someLockName"),但是如果您可以显式声明锁名,则可以对锁字段名放心使用解决方案一。

,

核心问题是您将调用真实方法与模拟而不是间谍结合在一起。 这通常很危险,因为它是否适用于任何东西在很大程度上取决于所讨论方法的内部实现。

Lombok之所以重要,是因为它通过在编译过程中更改内部实现来起作用,而这种方式恰好需要正确的对象初始化才能在原始方法不起作用的情况下起作用。

如果您要配置一个模拟来调用真实方法,则应该改用间谍。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。