Mockito - ошибка "Разыскивается, но не вызывается; однако были и другие взаимодействия с этим макетом" - PullRequest
13 голосов
/ 31 октября 2011

Я проверяю, что функция была вызвана с использованием 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();                       
}

1 Ответ

25 голосов
/ 31 октября 2011

У Mockito до версии 1.8.5 была ошибка в случае полиморфной отправки. Это было исправлено и доступно в первом выпуске кандидата версии 1.9.0. См. выпуск 200 .

Так как же это происходит в вашей кодовой базе? Обратите внимание, что вы издеваетесь над этими двумя классами

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

AutoIndexer оказывается родителем общего интерфейса, в этом интерфейсе есть метод ReadableIndex<T> getAutoIndex(). RelationshipAutoIndexer является подтипом AutoInexer, где общая часть имеет фиксированное значение Relationship, и переопределяет метод getAutoIndex() для возврата ковариантного типа ReadableRelationshipIndex.

См. AutoIndexer и RelationshipIndexer .

Ну, в вашем коде вызова есть следующие строки:

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

Как nodeAutoIndex в вашем рабочем коде, так и макет nodeAutoIndexer в вашем тестовом коде имеют ссылку типа AutoIndexer<Node>, поэтому проблем с полиморфной отправкой нет. Однако на relAutoIndex в вашем рабочем коде ссылается тип AutoIndexer<Relationship>, а на макет relAutoIndexer в вашем тестовом коде ссылается тип RelationshipAutoIndexer, поэтому неправильный вызов регистрируется на макете, а затем не проходит проверку.

Ваше решение - либо обновить версию mockito ; 1.9.0 RC1 очень стабилен, и финальный релиз должен быть на вашем пути. Или вы можете перенести ссылочный тип (в вашем производственном коде) из:

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

до:

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

Несколько других замечаний.

  • На самом деле вам не нужно писать здесь метод after, так как JUnit создает новый экземпляр при каждом запуске метода, поэтому ваш метод просто добавляет код, который будет выполнен в любом случае. Обратите внимание, что это не относится к TestNG.

  • Вместо создания насмешек в методе before вы можете использовать аннотации Mockito. Не забудь бегуна.

Например:

@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);
...