Как с помощью Mockito сопоставить пару "ключ-значение" карты?

Мне нужно отправить конкретное значение из фиктивного объекта на основе определенного значения ключа.

Из конкретного класса:

map.put("xpath", "PRICE");
search(map);

Из тестового примера:

IOurXMLDocument mock = mock(IOurXMLDocument.class);
when(mock.search(.....need help here).thenReturn("$100.00");

Как издеваться над вызовом этого метода для этой пары "ключ-значение"?


person Sean    schedule 05.04.2010    source источник


Ответы (5)


Я обнаружил, что это пытается решить аналогичную проблему, создав заглушку Mockito с параметром Map. Я не хотел писать настраиваемый сопоставитель для рассматриваемой карты, и затем я нашел более элегантное решение: использовать дополнительные сопоставители в hamcrest-library с mockito argThat:

when(mock.search(argThat(hasEntry("xpath", "PRICE"))).thenReturn("$100.00");

Если вам нужно проверить несколько записей, вы можете использовать другие полезности Hamcrest:

when(mock.search(argThat(allOf(hasEntry("xpath", "PRICE"), hasEntry("otherKey", "otherValue")))).thenReturn("$100.00");

Это начинает затягиваться с нетривиальными картами, поэтому я закончил извлечение методов для сбора сопоставлений записей и вставил их в наш TestUtils:

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.hasEntry;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.hamcrest.Matcher;
---------------------------------
public static <K, V> Matcher<Map<K, V>> matchesEntriesIn(Map<K, V> map) {
    return allOf(buildMatcherArray(map));
}

public static <K, V> Matcher<Map<K, V>> matchesAnyEntryIn(Map<K, V> map) {
    return anyOf(buildMatcherArray(map));
}

@SuppressWarnings("unchecked")
private static <K, V> Matcher<Map<? extends K, ? extends V>>[] buildMatcherArray(Map<K, V> map) {
    List<Matcher<Map<? extends K, ? extends V>>> entries = new ArrayList<Matcher<Map<? extends K, ? extends V>>>();
    for (K key : map.keySet()) {
        entries.add(hasEntry(key, map.get(key)));
    }
    return entries.toArray(new Matcher[entries.size()]);
}

Итак, у меня осталось:

when(mock.search(argThat(matchesEntriesIn(map))).thenReturn("$100.00");
when(mock.search(argThat(matchesAnyEntryIn(map))).thenReturn("$100.00");

С дженериками связано некоторое уродство, и я подавляю одно предупреждение, но, по крайней мере, оно СУХОЕ и спрятано в TestUtil.

И последнее замечание: остерегайтесь проблем со встроенным hamcrest в JUnit. 4.10. С Maven я рекомендую сначала импортировать библиотеку hamcrest, а затем JUnit 4.11 (теперь 4.12) и на всякий случай исключить hamcrest-core из JUnit:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

Изменить: 1 сентября 2017 г. - В некоторых комментариях я обновил свой ответ, чтобы показать свою зависимость Mockito, мой импорт в тестовой утилите и junit, который на сегодняшний день работает зеленым:

import static blah.tool.testutil.TestUtil.matchesAnyEntryIn;
import static blah.tool.testutil.TestUtil.matchesEntriesIn;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

public class TestUtilTest {

    @Test
    public void test() {
        Map<Integer, String> expected = new HashMap<Integer, String>();
        expected.put(1, "One");
        expected.put(3, "Three");

        Map<Integer, String> actual = new HashMap<Integer, String>();
        actual.put(1, "One");
        actual.put(2, "Two");

        assertThat(actual, matchesAnyEntryIn(expected));

        expected.remove(3);
        expected.put(2, "Two");
        assertThat(actual, matchesEntriesIn(expected));
    }

    @Test
    public void mockitoTest() {
        SystemUnderTest sut = mock(SystemUnderTest.class);
        Map<Integer, String> expected = new HashMap<Integer, String>();
        expected.put(1, "One");
        expected.put(3, "Three");

        Map<Integer, String> actual = new HashMap<Integer, String>();
        actual.put(1, "One");

        when(sut.search(argThat(matchesAnyEntryIn(expected)))).thenReturn("Response");
        assertThat(sut.search(actual), is("Response"));
    }

    protected class SystemUnderTest {
        // We don't really care what this does
        public String search(Map<Integer, String> map) {
            if (map == null) return null;
            return map.get(0);
        }
    }
}
person Marquee    schedule 28.03.2014
comment
Это удобное решение. Любопытно, что я получал неверный тип аргумента, ожидая, что Map ‹String, String› found Map ‹? расширяет String,? extends String ›, которую Java не могла решить с помощью каких-либо изменений и настроек импорта. Мне пришлось изменить метод своего имитируемого класса, чтобы он принимал Map<? extends String, ? extends String>, чтобы заставить его работать. Есть предположения? У меня такие же версии зависимостей для junit и hamcrest, что и у вас; у меня Mockito v. 1.9.5. - person Patrick M; 08.04.2015
comment
@PatrickM удалось ли вам решить проблему с дженериками более чистым способом? Из приведенного выше у меня точно такая же проблема, но я не могу изменить общие типы коллекции ... - person Andrew Eells; 10.02.2016
comment
@AndrewEells, нет, я так и не нашел решения. Я перешел с Hamcrest и начал использовать AssertJ, который, похоже, требует гораздо меньше приведения типов для своих утверждений. - person Patrick M; 10.02.2016
comment
@PatrickM, AndrewEells - я не видел вашей проблемы. Я воссоздал тестовую утилиту и тестовый пример, используя вышеизложенное, и он все еще работает. Также интересно узнать о вашем решении AssertJ, может, вы могли бы поделиться им в качестве нового ответа? - person Marquee; 01.09.2017
comment
Хм, теперь, когда вы упомянули об этом, у AssertJ на самом деле нет решения для удаления требования на сопоставители Hamcrest из интерфейса Mockito when. Вот где у меня возникла проблема с приведением типа, а не с утверждениями, как я указал в моем предыдущем комментарии. Я даже не могу вспомнить, какой проект мне пришлось делать, они настраивали тип, не говоря уже о модульном тесте. (Но я почти уверен, что мне пришлось оставить это с синтаксисом ? extends String.) - person Patrick M; 01.09.2017
comment
Незначительное улучшение - используйте анонимные классы init для заполнения карт, например: new HashMap<Integer, String>() {{ put(1, "One"); put(2, "Two"); }}; и т. Д. - person d4vidi; 11.02.2019

Если вы просто хотите сопоставить с определенной картой, вы можете использовать некоторые из приведенных выше ответов или настраиваемый объект сопоставления, который расширяет Map ‹X, Y› или ArgumentCaptor, например:

ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
verify(mock, times(1)).method((Map<String, String>) argumentsCaptured.capture());
assert argumentsCaptured.getValue().containsKey("keynameExpected"); 
// argumentsCaptured.getValue() will be the first Map it called it with.
// argumentsCaptured.getAllValues() if it was called more than times(1)

См. Также дополнительные ответы здесь: Проверить значение атрибута объекта с помощью mockito

Если вы хотите сделать снимок нескольких карт:

ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map> argumentsCaptured2 = ArgumentCaptor.forClass(Map.class);
verify(mock, times(1)).method(argumentsCaptured.capture(), argumentsCaptured2.capture());
assert argumentsCaptured.getValue().containsKey("keynameExpected"); 
assert argumentsCaptured2.getValue().containsKey("keynameExpected2"); 
....
person rogerdpack    schedule 29.10.2014
comment
Обратите внимание, что этот ответ работает также, если вы хотите проверить равенство только некоторых пар ключ / значение между ожидаемым и фактическим. Это удобно, если некоторые значения нельзя высмеивать. +1 голос - person avi.elkharrat; 25.02.2020
comment
Что, если метод требует передачи и захвата нескольких карт? - person RamPrasadBismil; 19.03.2021
comment
ОК добавил пример нескольких - person rogerdpack; 19.03.2021

Это не работает?

Map<String, String> map = new HashMap<String, String>();
map.put("xpath", "PRICE");
when(mock.search(map)).thenReturn("$100.00");

Параметр Map должен вести себя так же, как и другие параметры.

person Bozho    schedule 05.04.2010
comment
Отсутствует одна закрывающая скобка. - person stefanglase; 06.04.2010
comment
IOurXMLDocument представляет наш уровень обслуживания, который я не хочу вызывать для своих модульных тестов. Для одной ситуации мы вызываем ее дважды с двумя разными значениями карты. Вместо этого я хочу проверить значение и вернуть фиксированный результат. Поэтому, когда код приложения выполняет следующие действия: map.put (xpath, PRODUCTNAME); когда (mock.search (карта)). thenReturn (Candybar); - person Sean; 06.04.2010
comment
Не должно быть mock.search (eq (map)), чтобы он проверял фактическое равенство карты? - person fikovnik; 28.11.2015
comment
это не работает. Использование сопоставления равенства на карте тоже не работает. - person drndivoje; 10.09.2018

Похоже, что вам нужен Answer:

IOurXMLDocument doc = mock(IOurXMLDocument.class);
when(doc.search(Matchers.<Map<String,String>>any())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
        Map<String, String> map = (Map<String, String>) invocation.getArguments()[0];
        String value = map.get("xpath");
        if ("PRICE".equals(value)) {
            return "$100.00";
        } else if ("PRODUCTNAME".equals(value)) {
            return "Candybar";
        } else {
            return null;
        }
    }
});

Но кажется, что лучше не использовать примитив Map в качестве параметра вашего метода поиска - вы, вероятно, могли бы преобразовать эту карту в pojo с атрибутами price и productName. Просто идея :)

person denis.solonenko    schedule 09.03.2011

Опоздал на вечеринку на 11 лет, но org.mockito.Matchers#anyMapOf работал на меня. В примере OP это будет:

when(mock.search(anyMapOf(String.class, String.class))).thenReturn("$100.00");
person AlexMA    schedule 15.07.2021