15

我正在验证是否使用 Mockito 调用了一个函数,但 Mockito 告诉我从未调用过我正在验证的函数,并且调用了其他函数。但在我看来,我正在调用正确的函数......

这是我收到的错误的堆栈跟踪:

Wanted but not invoked:
relationshipAutoIndexer.getAutoIndex();
-> at org.whispercomm.manes.server.graph.DataServiceImplTest.testInitIndices(DataServiceImplTest.java:117)

However, there were other interactions with this mock:
-> at org.whispercomm.manes.server.graph.DataServiceImpl.updateIndexProperties(DataServiceImpl.java:136)
-> at org.whispercomm.manes.server.graph.DataServiceImpl.updateIndexProperties(DataServiceImpl.java:144)
-> at org.whispercomm.manes.server.graph.DataServiceImpl.updateIndexProperties(DataServiceImpl.java:148)
-> at org.whispercomm.manes.server.graph.DataServiceImpl.updateIndexProperties(DataServiceImpl.java:149)
-> at org.whispercomm.manes.server.graph.DataServiceImpl.initIndices(DataServiceImpl.java:121)

    at org.whispercomm.manes.server.graph.DataServiceImplTest.testInitIndices(DataServiceImplTest.java:117)

它发生在

verify(relAutoIndexer).getAutoIndex();

测试类代码如下所示。

这是我的代码(我倾向于无意中遗漏一些内容。请向我询问您认为我缺少的任何代码,我会添加它):

public DataServiceImpl(GraphDatabaseService graphDb) {
    super();
    this.graphDb = graphDb;
    unarchivedParent = new UnarchivedParent(graphDb.createNode());
    archivedParent = new ArchivedParent(graphDb.createNode());
    packetParent = new PacketParent(graphDb.createNode());
    userParent = new UserParent(graphDb.createNode());
    this.initIndices();
}

/**
 * Initializes the node and relationship indexes.
 * 
 * Updates the set of indexed properties to match {@link DataServiceImpl}
 * .NODE_KEYS_INDEXABLE and {@link DataServiceImpl}.REL_KEYS_INDEXABLE.
 * 
 * Note: auto indices can also be configured at database creation time and
 * just retrieved at runtime. We might want to switch to that later.
 */
private void initIndices() {
    /* Get the auto-indexers */
    AutoIndexer<Node> nodeAutoIndexer = this.graphDb.index()
            .getNodeAutoIndexer();

    AutoIndexer<Relationship> relAutoIndexer = this.graphDb.index()
            .getRelationshipAutoIndexer();

    this.updateIndexProperties(nodeAutoIndexer,
            DataServiceImpl.NODE_KEYS_INDEXABLE);

    this.nodeIndex = nodeAutoIndexer.getAutoIndex();

    this.updateIndexProperties(relAutoIndexer,
            DataServiceImpl.REL_KEYS_INDEXABLE);

    this.relIndex = relAutoIndexer.getAutoIndex();
}

/**
 * Sets the indexed properties of an {@link AutoIndexer} to the specified
 * set, removing old properties and adding new ones.
 * 
 * @param autoIndexer
 *            the AutoIndexer to update.
 * @param properties
 *            the properties to be indexed.
 * @return autoIndexer, this given AutoIndexer (useful for chaining calls.)
 */
private <T extends PropertyContainer> AutoIndexer<T> updateIndexProperties(
        AutoIndexer<T> autoIndexer, Set<String> properties) {
    Set<String> indexedProps = autoIndexer.getAutoIndexedProperties();
    // Remove unneeded properties.
    for (String prop : difference(indexedProps, properties)) {
        autoIndexer.stopAutoIndexingProperty(prop);
    }

    // Add new properties.
    for (String prop : difference(properties, indexedProps)) {
        autoIndexer.startAutoIndexingProperty(prop);
    }

    // Enable the index, if needed.
    if (!autoIndexer.isEnabled()) {
        autoIndexer.setEnabled(true);
    }

    return autoIndexer;
}

这是测试类的代码:

@Before
public void setup() {
   nA = mock(Node.class);
   nB = mock(Node.class);
   packetA = new PacketWrapper(nA);
   packetB = new PacketWrapper(nB);
   RelA = mock(Relationship.class);
   RelB = mock(Relationship.class);
   graphDb = mock(GraphDatabaseService.class);
   nodeAutoIndexer = (AutoIndexer<Node>) mock(AutoIndexer.class);
       relAutoIndexer = mock(RelationshipAutoIndexer.class);
}

@After
public void tearDown() {
  packetA = null;
  packetB = null;
}
/*
 * ---------------- Test initIndices() ---------------
 */
//TODO
@Test
public void testInitIndices() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
   IndexManager indexManager = mock(IndexManager.class);
   when(graphDb.index()).thenReturn(indexManager);
   when(indexManager.getNodeAutoIndexer()).thenReturn(nodeAutoIndexer);
       when(graphDb.index().getRelationshipAutoIndexer()).thenReturn(relAutoIndexer);
   dataService = new DataServiceImpl(graphDb);
       verify(nodeAutoIndexer, atLeastOnce()).getAutoIndex();
       verify(relAutoIndexer).getAutoIndex();                       
}
4

1 回答 1

26

Mockito,直到版本 1.8.5,在多态调度的情况下有一个错误。它已修复,可在版本 1.9.0 的第一个候选版本中使用。第 200 期

那么它是如何在您的代码库中发生的。请注意,您正在模拟这两个类

nodeAutoIndexer = (AutoIndexer<Node>) mock(AutoIndexer.class);
relAutoIndexer = mock(RelationshipAutoIndexer.class);

AutoIndexer碰巧是一个通用的父接口,在这个接口里面有这个方法 ReadableIndex<T> getAutoIndex()RelationshipAutoIndexerAutoInexer泛型部分固定到的子类型Relationship,并覆盖该getAutoIndex()方法以返回协变类型ReadableRelationshipIndex

请参阅自动索引器关系索引器。

好吧,在您的调用代码中,您有以下几行:

AutoIndexer<Node> nodeAutoIndexer = this.graphDb.index().getNodeAutoIndexer();
AutoIndexer<Relationship> relAutoIndexer = this.graphDb.index().getRelationshipAutoIndexer();
this.nodeIndex = nodeAutoIndexer.getAutoIndex();
this.relIndex = relAutoIndexer.getAutoIndex();

nodeAutoIndex您的生产代码和测试代码中的模拟都有nodeAutoIndexertype 的引用AutoIndexer<Node>,因此多态调度没有问题。但是relAutoIndex,在您的生产代码中由 type 引用,而您的测试代码中AutoIndexer<Relationship>的 mock由 type 引用,因此在 mock 上注册了错误的调用,然后验证失败。relAutoIndexerRelationshipAutoIndexer

您的解决方案是升级 mockito 版本;1.9.0 RC1 非常稳定,最终版本应该即将发布。或者您可以从以下位置迁移您的引用类型(在您的生产代码中):

AutoIndexer<Relationship> relAutoIndexer = this.graphDb.index().getRelationshipAutoIndexer();

到 :

RelationshipAutoIndexer relAutoIndexer = this.graphDb.index().getRelationshipAutoIndexer();

其他几点说明。

  • 您实际上不需要在此处编写 after 方法,因为 JUnit 在每个方法运行时都会创建一个新实例,因此您的方法只需添加无论如何都会完成的代码。请注意,TestNG 并非如此。

  • 您可能希望使用 Mockito 注释,而不是在 before 方法中创建模拟。不要忘记跑步者。

例如 :

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
    @Mock SomeType someTypeMock;
    // ...
}
  • 由于几个原因,存根代码有点难看。

    • 您应该编写一致的存根。

为什么不以更简洁的方式编写它;例如indexManager在这两种情况下引用:

IndexManager indexManager = mock(IndexManager.class);
when(graphDb.index()).thenReturn(indexManager);
when(indexManager.getNodeAutoIndexer()).thenReturn(nodeAutoIndexer);
when(indexManager.getRelationshipAutoIndexer()).thenReturn(relAutoIndexer);

或者根本不引用它

IndexManager indexManager = mock(IndexManager.class);
when(graphDb.index()).thenReturn(indexManager);
when(graphDb.index().getNodeAutoIndexer()).thenReturn(nodeAutoIndexer);
when(graphDb.index().getRelationshipAutoIndexer()).thenReturn(relAutoIndexer);

还有一个返回模拟的模拟通常是设计气味的标志。你打破了得墨忒耳法则,打破它意味着你将经历艰难的测试、糟糕的可维护性和艰难的进化。当我说你也能听到我的耳语时(没有三段论):这将花费你的钱。不要编写遗留代码!如果您正在练习 TDD 或 BDD,您将在设计时为您自己的代码识别这些问题,这样可以很好地防止它们。

  • 但是,如果您正在处理遗留代码,则可以使用这种深度存根语法:

使用静态方法你可以写这个

GraphDatabaseService graphdb = mock(GraphDatabaseService.class, RETURNS_DEEP_STUBS);

或者使用注释你可以这样写:

@Mock(answer = RETURNS_DEEP_STUBS) GraphDatabaseService graphdb;

和存根:

when(graphDb.index().getNodeAutoIndexer()).thenReturn(nodeAutoIndexer);
when(graphDb.index().getRelationshipAutoIndexer()).thenReturn(relAutoIndexer);
于 2011-10-31T14:26:35.210 回答