Mockito — «Требуется, но не вызывается; Однако были и другие взаимодействия с этой фиктивной» ошибкой

#java #junit #mockito #verify

#java #junit #mockito #проверить

Вопрос:

Я проверяю, что функция была вызвана с помощью 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. У меня была аналогичная проблема при выполнении полиморфных вызовов. Я обновился до версии Mockito 1.9.0-rc1. Это не решило мою проблему. Кто-нибудь еще испытывает это?

Ответ №1:

В 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 .

См. Автоиндексатор и 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. Не забудьте runner.

Например :

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

Комментарии:

1. Спасибо, потребовалось хорошее объяснение. Нет, на самом деле всегда требуется хорошее объяснение 😉

2. Это, вероятно, лучший ответ, который я когда-либо получал в stackoverflow. Мой первоначальный вопрос был решен, но когда я вызываю GraphDB.index().getNodeAutoIndexer(), это объекты / методы в загруженной библиотеке (neo4j), так что это не мой собственный код. Так все еще ли плохая практика, чтобы макет возвращал макет? Я не вижу другого способа проверить это.

3. Есть даже другое высказывание: не издевайтесь над типами, которыми вы не владеете. Однако, чтобы избежать таких утверждений, как graphDb.index().getNodeAutoIndexer() , вы должны разделить проблемы вашего кода. Например, вам GraphDatabaseService нужно GraphDB , может быть, у вас может быть только ссылка на IndexManager . Если это невозможно, вы можете разделить свои проблемы с индексацией в новом объекте, который будет принимать IndexManager . Или вы можете позволить это здесь, потому что ваш общий дизайн достаточно хорош, чтобы он не нарушал Закон Деметры в этом самом месте. Это ваш вызов 😉