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

Мокито, до версии 1.8.5, имел ошибку в случае полиморфной отправки. Он был исправлен и доступен в первом кандидате на выпуск версии 1.9.0. См. issue 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();

to:

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

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

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

  • Вместо того, чтобы создавать свои mocks в методе 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);