Inżynieria wymagańJavaTrip and tricks

Mockito wprowadzenie i przykłady

Mockito jest biblioteką udostępniającą API do tworzenia mokowalnych obiektów w Javie. Obiekt mokowany to nic innego niż atrapa implementacja danego obiektu. Programista definiuje, co dany obiekt ma robić, bez samodzielnego powoływanie jego instancji wraz z uzupełnianiem wszystkich właściwości.

Mockito dołączamy jako bibliotekę do projektu. Aby w pełni korzystać z jej dobrodziejstw trzeba do projektu dołączyć również bibliotekę JUnit.

W tym miejscu zwrócę uwagę, że najczęściej spotykaną notacją dla tworzenia nazw metod testowych nie jest popularny camelCase lecz nazwy_oddzielone_podłogami. Dobrą praktyka jest, aby nazwy metod testujących nazywały się tak samo jak nazwy metod testowanych zgodnie z wyżej zapisaną konwersją. W przypadku, gdy wiele metod pokrywa sprawdzenie jednej metody biznesowej, to do metod testowych dopisuje się indeksy.

Projekt szablonowy wygląda następująco:


@RunWith(MockitoJUnitRunner.class)
public class SampleTest {

@Mock
 // inicjalizacja obiektów

@Before
 public void init() {
 // konfiguracja obiektów
 }
 
 @Test
 public void business_method_1() {
 // logika testująca
 } 
 
 @Test
 public void business_method_2() {
 // logika testująca
 }

@After
 public void finish() {
 // porządkowanie, logowanie
 }
}

Aby skorzystać Mockito klasę testu musimy opatrzyć adnotacją @RunWith(MockitoJUnitRunner.class). Jak widać mamy wyszczególnione 4 sekcje, reprezentowane przez adnotacje:
• @Mock – wstrzykiwanie obiektu, który będzie mockowany;
• @Before – wykonywane przed rozpoczęciem KAŻDORAZOWO wewnątrz klasy testu;
• @Test – logika testu;
• @After – wywoływane po KAŻDORAZOWYM zakończeniu testu np. w celu posprzątania obiektu itp.

Najlepiej jest przestawić działanie Mockito na kilku przypadkach zamiast budowienie obszernego przykładu i po kolei tłumaczenie zasad działania.

Przykład 1 – Ogólny


klasa biznesowa:
public class MyCollection implements Collection<String> {

private Object[] myTable = new Object[20];
 private int counter;

@Override
 public int size() {
 for (Object object : myTable) {
 counter++;
 }
 return counter;
 }
 
 ...
}

klasa testująca:
@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.class)
public class SampleTest {

@Mock
 MyCollection myCollection;

@Test
 public void business_method_1() {
 when(myCollection.size()).thenReturn(20);
 }
}

Klasa z logiką biznesową implementuje tablicę 20 elementów. W teście z użyciem metody when – dokonywane jest sprawdzenie, czy wartość zwracana z metody myCollection.size() jest zgodna (thenReturn) z wartością 20.

Przykład 2 – Tworzenie mocków agregujących mocki.

Zdarza się tak, że obiekt który chcemy zmockować wymaga innego obiektu do przekazania. Np. w poniższym przykładzie do utworzenia instancji klasy Person wymagany jest obiekt klasy Car.

public class Person {
 
 private Car car;

public Person(Car car) {
 this.car = car;
 } 
}

Wewnątrz klasy testu dopuszcza się realizacji w dwojaki sposób:

@RunWith(MockitoJUnitRunner.class)
public class SampleTest {

@Mock
 Car car;

@InjectMocks
 Person person;
}

@Mock tworzy makiety. @InjectMocks tworzy instancję klasy i wstrzykuje do niej klasy opatrzone adnotacją @Mock.

Uwaga!
Zastosowanie ten adnotacji @InjectMocks powoduje, że obiekt zostanie zainicjalizowany przy ładowaniu klasy. Metody testujące mogą dokonywać zmiany wewnątrz tego obiektu. Przykładko metoda_1 dokona zmiany w obiekcie, ten sam obiekt ze zmienionym stanem trafi do metoda_2, co może spowodować, że otrzymamy inny rezultat testu niż oczekiwany. Dlatego zalecane jest aby w metodzie @After przywrócić wcześniejszy stan obiektu. Obejściem jest zastosowanie sposobu 2, niestety kosztem zużycia większych zasobów sprzętowych.

2) Wstrzykiwanie za pomocą konstruktora

@RunWith(MockitoJUnitRunner.class)
public class SampleTest {

@Mock
 Car car;

Person person;

@Before
 public void setUp() {
 person = new Person(car);
 }
}

Przykład 3 – Testowanie wielokrotnego wykorzystania metody

Istnieje możliwość sprawdzenia ile razy została wykorzystana dana metoda w czasie testu np:

@Test
 public void business_method_1() {
 myCollection.size();
 myCollection.size();
 verify(myCollection, times(2)).size();
 }

W powyższym przypadku metoda została wywołana dwa razy. Z użyciem metody verify oraz times() dokonywane jest sprawdzenie, czy ilość wywołań pokrywa się z oczekiwanym rezultatem. Oczywiście przykład jest prozaiczny, ale logika może dotykać zaawansowanych aspektów programowania rekurencyjnego.

Przykład 4 – Dowolne parametry

W czasie parametryzowania metody testującej może zdarzyć się konieczność przekazania obiektu/wartości, która nie ma dla nas znaczenia. Obiekty takie można symulować wykorzystując metodę “anyTyp()” np: anyObject(), anyShort(), anyFloat(), anyString(). Poniższy przykład prezentuje, że nie zależnie od tego jaki ciąg tekstowy jest przekazywany do metody, to spodziewaną wartością zwracaną jest true. Kod prezentuje trochę inną składnie, ale zapis any(String.class) jest równowartościowy z zapisem anyString().

@Test
public void business_method_1() {
     when((myCollection).add(any(String.class))).thenReturn(true);
}
Tags:
Show Buttons
Hide Buttons