【问题标题】:Mocking EntityManager模拟 EntityManager
【发布时间】:2015-04-06 10:26:58
【问题描述】:

我在模拟 EntityManager 时遇到了 NPE,下面是我的代码,

@Stateless
public class NodeChangeDeltaQueryBean implements NodeChangeDeltaQueryLocal {

    @PersistenceContext
    private EntityManager em;
    @Override
    public String findIdByNaturalKey(final String replicationDomain, final int sourceNodeIndex,
                                     final int nodeChangeNumber) {
        List<String> result =
            NodeChangeDelta.findIdByNaturalKey(this.em, replicationDomain, sourceNodeIndex,
                nodeChangeNumber).getResultList();
        return result.isEmpty() ? null : result.get(0);
    }
}

我的实体类

@Entity
public class NodeChangeDelta implements Serializable, Cloneable, GeneratedEntity, KeyedEntity<String> {

public static TypedQuery<String> findIdByNaturalKey(final EntityManager em, final String replicationDomain, final int sourceNodeIndex, final int nodeChangeNumber) {
        return em.createNamedQuery("NodeChangeDelta.findIdByNaturalKey", String.class)
            .setParameter("replicationDomain", replicationDomain)
            .setParameter("sourceNodeIndex", sourceNodeIndex)
            .setParameter("nodeChangeNumber", nodeChangeNumber);
    }
}

我的测试课

@RunWith(MockitoJUnitRunner.class)
public class NodeChangeDeltaQueryBeanTest {

    @InjectMocks
    NodeChangeDeltaQueryBean nodeChangeDeltaQueryBean;

    @Mock
    EntityManager em;

@Test
    public void testFindIdByNaturalKey() {
        this.addNodeChangeDelta();
        this.nodeChangeDeltaQueryBean.findIdByNaturalKey(this.REPLICATION_DOMAIN,
            this.SOURCE_NODE_INDEX, this.NODE_CHANGE_NUMDER);
    }
}

虽然调试 em 不为空(还有其他参数 REPLICATION_DOMAIN, SOURCE_NODE_INDEX, NODE_CHANGE_NUMDER not null) 在 Entity 类中,而 em.createNamedQuery("NodeChangeDelta.findIdByNaturalKey", String.class) 为 null。

【问题讨论】:

  • 您希望您的模拟程序执行实际查询吗?那是行不通的。它不知道您的数据源、持久性上下文、命名查询等。

标签: testing junit mockito entitymanager


【解决方案1】:

在 mockito wiki 上:Don't mock types you don't own !

这不是一条硬线,但越过这条线可能会产生影响! (很可能会。)

  1. 想象一下模拟第三方库的代码。在对第三个库进行特定升级后,逻辑可能会发生一些变化,但测试套件会执行得很好,因为它是模拟的。所以后来,认为一切都很好,毕竟构建墙是绿色的,软件已经部署并且...... 繁荣
  2. 这可能表明当前设计与此第三方库的分离不够充分。
  3. 另外一个问题是第三方库可能很复杂,甚至需要大量模拟才能正常工作。这会导致过度指定的测试和复杂的固定装置,这本身就会损害紧凑和可读的目标。或者因为模拟外部系统的复杂性而没有足够覆盖代码的测试。

相反,最常见的方法是围绕外部库/系统创建包装器,但应注意抽象泄漏的风险,即过多的低级 API、概念或异常超出了包装器的边界.为了验证与第三方库的集成,编写集成测试,并使其尽可能紧凑和可读。

您没有控制权的 Mock 类型可以被视为 (mocking) 反模式。虽然EntityManager 几乎是标准,但不应考虑在即将发布的 JDK / JSR 版本中不会有任何行为变化(它已经在 API 的其他部分发生了很多次,只需查看JDK 发行说明)。此外,真正的实现可能在其行为中存在难以被嘲笑的微妙之处,测试可能是绿色的,但生产中的 tomcat 却着火了(真实故事)。

我的观点是,如果代码需要模拟我不拥有的类型,则设计应该尽快改变,这样我、我的同事或该代码的未来维护者就不会陷入这些陷阱。

还有其他博客条目的 wiki 链接描述了他们在尝试模拟他们无法控制的类型时遇到的问题。

相反,我真的建议大家在测试与其他系统的集成时不要使用模拟。我相信对于数据库的东西,Arquillian 是最好的选择,该项目似乎非常活跃。


改编自我的回答:https://stackoverflow.com/a/28698223/48136

【讨论】:

    【解决方案2】:

    在 Mockito 中,任何未显式配置的 mock 上的方法调用总是返回 null。因此在findIdByNaturalKey 中,em.createNamedQuery 返回nullsetParameter 上的NPE。您需要将其配置为RETURN_MOCKS

    另外,我不确定@InjectMocks 是否支持@PersistenceContext。如果不是,那么em 可能为空。如果是,请告诉我,以上是您的问题。

    【讨论】:

    • @InjectMocks 不知道 spring / jee,它只是尝试找到匹配的类型。它不是一个真正的依赖注入框架。
    猜你喜欢
    • 2014-06-16
    • 2011-05-16
    • 2021-02-01
    • 2014-12-17
    • 2017-06-29
    • 1970-01-01
    • 2018-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多