Jak zweryfikować wiele wywołań metod z różnymi parametrami

116

Mam następującą metodę, na której chcę sprawdzić zachowanie.

public void methodToTest(Exception e, ActionErrors errors) {
    ...

    errors.add("exception.message", 
            ActionMessageFactory.createErrorMessage(e.toString()));

    errors.add("exception.detail",
            ActionMessageFactory.createErrorMessage(e.getStackTrace()[0].toString()));

    ...
}

W mojej klasie @Test miałem nadzieję zrobić coś takiego, aby sprawdzić, czy errors.add()jest wywoływane z „wyjątkiem.message” i ponownie z „wyjątkiem.detail”

verify(errors).add(eq("exception.message"), any(ActionError.class));
verify(errors).add(eq("exception.detail"), any(ActionError.class));

jednak Mockito narzeka w następujący sposób

Argument(s) are different! Wanted:
actionErrors.add(
    "exception.message",
    <any>
);

Actual invocation has different arguments:
actionErrors.add(
    "exception.detail",
    org.apache.struts.action.ActionError@38063806
);

Jak mogę powiedzieć Mockito, aby sprawdzał obie wartości?

Ćwiek
źródło
1
jeśli masz 2 metody z różnymi podpisami, możesz napisać osobny przypadek testowy dla obu.
Naveen Babu,
8
Tak, ale w tym przypadku jest to ta sama sygnatura metody, ale tylko inne wartości argumentów
Brad
możesz spróbować użyćMockito.reset()
takacsot

Odpowiedzi:

102

Dalsza lektura skłoniła mnie do wypróbowania ArgumentCaptors i następnych prac, chociaż znacznie bardziej rozwlekłych, niż bym chciał.

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);

verify(errors, atLeastOnce()).add(argument.capture(), any(ActionMessage.class));

List<String> values = argument.getAllValues();

assertTrue(values.contains("exception.message"));
assertTrue(values.contains("exception.detail"));
Ćwiek
źródło
czy istnieje sposób, aby upewnić się, że pewne parametry zostały sparowane przy użyciu tego podejścia? Załóżmy na przykład, że metoda OP miała dwa argumenty i chciał sprawdzić, czy zostały one wywołane razem
popełniono i
1
Przypadek testowy OP wywołuje methodToTest()dokładnie raz, dlatego ta odpowiedź weryfikuje, czy oba wywołania są wykonane razem. Przechwycony List<String> valueselement, który jest potwierdzany, będzie zawierał tylko dwie testowane wartości i żadnych innych. Możesz też dodać assertTrue(values.size == 2). Jeśli tego chcesz, zastąpiłbym 3 stwierdzenia assertTrue pojedynczym Hamcrestem ...assertThat(values, contains("exception.message", "exception.detail"));
Brad,
czy OP nie wywołuje przypadku testowego metody methodToTest () dwukrotnie?
committedandroider
przepraszam, nie było jasne. Miałem na myśli scenariusz, w którym OP chciał sprawdzić, czy dwa argumenty zostały wywołane łącznie. Zatem sygnatura metody wyglądałaby podobnie do public void methodToTest (Exception e, Message m, ActionErrors errors) {tak, aby określony wyjątek był wywoływany z określoną wiadomością. Przypuszczałem, że może po prostu mieć dwa ArgumentCaptors a następnie pobrać indeks i porównać przy użyciu wartości z tych indeksów na obu listach wartości
committedandroider
Przypadek testowy OP wywołuje methodToTest()jeden raz. Jest to argument metody, który ActionErrors errorsjest wywoływany wewnętrznie dwukrotnie.
Brad
61

Jeśli kolejność obu add()połączeń jest istotna, możesz użyć InOrder:

InOrder inOrder = inOrder(errors);
inOrder.verify(errors).add(eq("exception.message"), any(ActionError.class));
inOrder.verify(errors).add(eq("exception.detail"), any(ActionError.class));
Christoph Walesch
źródło
7
Wystarczy przekazać pojedynczy errorsargument: InOrder inOrder = inOrder(errors);(patrz dokumentacja )
GreenhouseVeg
2
Co się stanie, jeśli zamówienie NIE jest istotne? co często się zdarza.
haelix
1
@haelix W takim przypadku użyj odpowiedzi Brads. Przekształcają Listsię Seti twierdzą, że zestaw wejść równa podanego zestawu przez zrzuty argument.
zrzut ekranu
25

Spróbuj czegoś takiego:

verify(errors, times(2))
     .add(AdditionalMatchers.or(eq("exception.message"), eq("exception.detail")),
          any(ActionError.class));
John B.
źródło
5
Twój czek jest oczywiście zbyt rozluźniony.
haelix
17

prawdopodobnie masz problem w swoim kodzie. Ponieważ w rzeczywistości piszesz ten kod:

Map<Character, String> map = mock(Map.class);

map.put('a', "a");
map.put('b', "b");
map.put('c', "c");

verify(map).put(eq('c'), anyString());
verify(map).put(eq('a'), anyString());
verify(map).put(eq('b'), anyString());

Zauważ, że pierwsza weryfikacja nie jest równa w odniesieniu do rzeczywistych wywołań.

Radziłbym również, aby właściwie nie kpić z typów, których nie jesteś właścicielem, np. Typu rozpórki.

[EDYCJA @Brad]

Po uruchomieniu kodu Brice'a (powyżej) w moim IDE widzę, że użyłem ActionError zamiast ActionMessage, dlatego moja weryfikacja () nie pasuje. Komunikat o błędzie, który opublikowałem początkowo, wprowadzał mnie w błąd, myśląc, że to pierwszy niezgodny argument. Okazuje się, że był to drugi argument.

Tak więc odpowiedź na moje pytanie brzmi

/** 
 * note that ActionMessageFactory.createErrorMessage() returns ActionMessage
 * and ActionError extends ActionMessage
 */
verify(errors).add(eq("exception.message"), any(ActionMessage.class));
verify(errors).add(eq("exception.detail"), any(ActionMessage.class));
Brice
źródło
1
Nie rozumiem tego, co próbujesz powiedzieć. Czy kolejność weryfikacji ma znaczenie? jeśli kolejność weryfikacji ma znaczenie. Dlaczego więc tutaj jest dostępny interfejs API InOrder?
Oleksandr Papchenko
Podobnie jak powyżej, kolejność weryfikacji jest nieistotna; dlatego jest InOrder.
Brice
12

Możesz użyć, Mockito.atLeastOnce()który pozwala Mockito przejść test, nawet jeśli ten mockObject będzie wywoływany wiele razy.

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(2));
sendon1982
źródło
1

1) Powiedz Mokito o całkowitym oczekiwaniu na rozmowy.

2) Powiedz Mokito, ile razy każda kombinacja parametrów była oczekiwana.

verify(errors, times(2)).add(any(), any(ActionMessage.class));

verify(errors, atLeastOnce()).add(eq("exception.message"), any());
verify(errors, atLeastOnce()).add(eq("exception.detail"), any());
epoks
źródło
0

W podobny sposób jak @ sendon1928 możemy użyć:

Mockito.times(wantedInvocationCount)

aby upewnić się, że metoda została wywołana dokładną liczbę razy (moim zdaniem preferowane rozwiązanie). Potem możemy zadzwonić

Mockito.verifyNoMoreInteractions(mock)

Aby upewnić się, że makieta nie była dalej używana w żadnym kontekście. Pełny przykład:

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(2));

Mockito.verifyNoMoreInteractions(mockObject)
Michał Powłoka
źródło