UnnecessaryStubbingException w Mockito
Spis treści
Korzystając z Mockito prędzej, czy później spotkamy się z wyjątkiem UnnecessaryStubbingException
. Jak nazwa wskazuje, występuje on w momencie, gdy dopuścimy się niepotrzebnego stubbowania, czyli nadamy funkcjonalność mockowi, z której nigdy nie skorzystamy.
Jako że kod mówi więcej niż tysiąc słów, przejdźmy od przykładu. Najpierw prosty serwis, który będziemy mockować:
public class Service {
public String doSomething(String input) {
return input + input;
}
}
Klasa testowa:
@ExtendWith(MockitoExtension.class)
public class StrictStubbingExample {
@Mock Service serviceMock;
@Test
public void shouldWorkJustFine() throws Exception {
// given
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
}
}
Uruchomienie spowoduje rzucenie wyjątku o treści:
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at pl.javastart.junittestingcourse.examples.mockito.strict.StrictStubbingExample.shouldWorkJustFine(StrictStubbingExample.java:28)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:186)
(...)
Z wyjątku wynika, że:
- został on rzucony, ponieważ mamy niepotrzebny kod, a to sprzeczne z ideą kodu, który jest czysty i łatwo się go utrzymuje
- został on rzucony w metodzie
MockitoExtension.afterEach()
Kiedy jest rzucany?
Zanim przejdziemy do możliwych rozwiązań spróbujmy zrozumieć kiedy dokładnie ten wyjątek jest rzucany. Jeśli zmienimy nieco kod i zamiast MockitoExtension
skorzystamy z np. MockitoAnnotations.openMocks()
//@ExtendWith(MockitoExtension.class)
public class StrictStubbingExample {
@Mock Service serviceMock;
@Test
public void shouldWorkJustFine() throws Exception {
// given
MockitoAnnotations.openMocks(this);
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
}
}
Wyjątek nie występuje. Tak samo, gdy utworzymy mocka bez użycia adnotacji
public class StrictStubbingExample {
@Test
public void shouldWorkJustFine() throws Exception {
// given
Service serviceMock = Mockito.mock(Service.class); // <-- tworzymy mocka "ręcznie"
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
}
Najwidoczniej MockitoExtension
ma w sobie jakiś dodatkowy mechanizm. Pogrzebałem trochę i ten test mniej więcej oddaje, co tam się dzieje:
@Test
public void shouldWorkJustFine() throws Exception {
MockitoSession mockitoSession = Mockito.mockitoSession()
.startMocking(); // start sesji
// given
Service serviceMock = Mockito.mock(Service.class);
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
mockitoSession.finishMocking(); // zamknięcie sesji i sprawdzenie nieużytych stubów
}
Przed każdym testem otwierana jest sesja, a po jego zakończeniu jest zamykana. No i właśnie podczas tego zamykania sprawdzane jest, czy każdy stub został wywołany co najmniej raz.
Jak to rozwiązać?
Usunąć niepotrzebne stuby
To zdecydowanie najlepszy pomysł. Trzeba się pozbyć tego, co niepotrzebne i gotowe. W tym przypadku wystarczy usunąć jeden ze stubów:
@Test
public void shouldWorkJustFine() throws Exception {
MockitoSession mockitoSession = Mockito.mockitoSession()
.startMocking();
// given
Service serviceMock = Mockito.mock(Service.class);
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
// Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
mockitoSession.finishMocking();
}
Wydaje się oczywiste, ale w przypadku gdy mamy dodany stub w @BeforeEach
i używany we wszystkich testach oprócz jednego, to powyższe rozwiązanie nie jest już takie proste.
Zmienić Strictness
Istnieje możliwość wyłączenia tego mechanizmu poprzez zmianę poziomu Strictness. Możemy to zrobić albo dla całej sesji. Do wyboru mamy:
Strictness.LENIENT
- wyłącza ten mechanizmStrictness.WARN
- drukuje ostrzeżenia w konsoliStrictness.STRICT
- domyślny, powoduje rzucenie wyjątku
@Test
public void shouldWorkJustFine() throws Exception {
MockitoSession mockitoSession = Mockito.mockitoSession()
.strictness(Strictness.LENIENT) // wyluzuj (domyśnie STRICT_STUBS)
.startMocking();
// given
Service serviceMock = Mockito.mock(Service.class);
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
mockitoSession.finishMocking();
}
Można również dla konkretnego stuba:
@Test
public void shouldWorkJustFine() throws Exception {
MockitoSession mockitoSession = Mockito.mockitoSession()
.strictness(Strictness.STRICT_STUBS)
.startMocking();
// given
Service serviceMock = Mockito.mock(Service.class);
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.lenient().when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
mockitoSession.finishMocking(); // nie rzuca wyjątku
}
Zmienić Strictness w MockitoExtension
W przypadku gdy korzystamy z @ExtendWith(MockitoExtension.class)
należy dodać adnotację @MockitoSettings
i wybrać odpowiedni poziom:
@MockitoSettings(strictness = Strictness.WARN) // domyśnie Strictness.STRICT
@ExtendWith(MockitoExtension.class)
public class StrictStubbingExample {
@Mock Service serviceMock;
@Test
public void shouldWorkJustFine() {
// given
Mockito.when(serviceMock.doSomething("a")).thenReturn("1");
Mockito.when(serviceMock.doSomething("b")).thenReturn("2"); // nigdy nie używane
// when
String result = serviceMock.doSomething("a");
// then
assertEquals(result, "1");
}
}
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.