Spock i Mock

W poprzednim wpisie przedstawiłem bibliotekę do testowania – Spock. Bardzo przyjemne narzędzie do testowania aplikacji napisanych w języku Java. Dzisiaj pogłębimy wiedzę o używanie mocków. Najpierw jednak przypomnę czym są mocki i dlaczego je stosujemy.

Co to jest mock?

Czym tak naprawdę jest mock? Aby odpowiedź była prosta i oczywista dla Ciebie, posłużę się przykładem.

Założenie: Testy jednostkowe (ang. unit tests) powinniy testować klasę w izolacji, tzn. sprawdzając tylko logikę zakodowaną w klasie bez wywołania zewnętrznych serwisów jeśli takie istnieją i są powiązane z działaniem klasy testowanej.

Wiemy, że zależności (ang. dependencies) w Javie wstrzykujemy do klasy poprzez konstruktor (ang. dependency injection).

Skoro chcemy przetestować klasę, która potrzebuje wstrzyknąć zewnętrzną zależność, to istnieje potrzeba wywołania zależności. Sytuacja ta łamie założenie o testowaniu w izolacji. Wobec czego potrzebujemy mechanizmu, który pozwoli ZASYMULOWAĆ działanie zależności bez konieczności prawdziwego jej wywołania.

Przykład ze Spock’iem

Spock: Co by tu zamokować?

Weźmy pod uwagę następującą klasę CargoDispatchHandler. Jak widać poniżej, logika biznesowa zawarta jest w metodzie handleCargoDispatch() i aby ją wywołać potrzebujemy zależności CargoDispatcher oraz CargoRepository.

public class CargoDispatchHandler {
   private CargoRepository repository;

   private CargoDispatcher cargoDispatcher;

   private List<Cargo> cargosToDispatch;

   public CargoDispatchHandler(CargoRepository repository, CargoDispatcher cargoDispatcher, List<Cargo> cargosToDispatch) {
       this.repository = repository;
       this.cargoDispatcher = cargoDispatcher;
       this.cargosToDispatch = cargosToDispatch;
   }

   public void handleCargoDispatch() {
       List<Cargo> knownCargos = repository.findByCargoTypeAndOwner();
       cargoDispatcher.dispatch(knownCargos, cargosToDispatch);
   }
}

Definicja mocka w Spock’u

Mock jest obiektem, który imituje prawdziwą zależność. Dzięki temu, możemy go wstrzyknąć tworząc klasę testową i wywołać na nim metody, które niesie ze sobą wstrzykiwana zależność. Niestety nie potrafi w jej imieniu działać, więc zadaniem programisty jest wskazanie mock’owi co tak naprawdę ma zwrócić.

Aby wskazać, że dany obiekt jest mock’iem w Spock’u wystarczy przypisać do niego specjalną metodę Mock().

Jak to działa z biblioteką Spock?

Poniższy przykład testu ilustruje wykorzystanie mocka :

class CargoDispatchHandlerSpec extends Specification {

   def CargoRepository repository = Mock()

   def CargoDispatcher cargoDispatcher = Mock()

   List<Cargo> cargosToDispatch = List.of(
           new Cargo("1", new Owner("Dan", 40), CargoType.MEDIUM, 50),
           new Cargo("5", new Owner("Shone", 30), CargoType.BIG, 100))

   CargoDispatchHandler dispatchHandler = new CargoDispatchHandler(repository, cargoDispatcher, cargosToDispatch)

   def "should handle cargo dispatch"() {
       given:
           List<Cargo> knownCargos = List.of(
                   new Cargo("1", new Owner("Dan", 40), CargoType.MEDIUM, 50),
                   new Cargo("3", new Owner("Gian", 35), CargoType.SMALL, 10))
       when:
           dispatchHandler.handleCargoDispatch()
       then:
           1 * repository.findByCargoTypeAndOwner() >> knownCargos
           1 * cargosToDispatch.dispatch(knownCargos, cargosToDispatch)
   }
}

Jak wspomniałem wcześniej, mock’a definiujemy przez wskazanie, który z obiektów jest mockiem. Używamy metody Mock().

Oczywiście pewne obiekty, niezbędne do działania klasy musimy przygotować.

Tworzymy obiekt klasy testowej poprzez wstrzyknięcie zależności (zamokowanych i obiektów wytworzonych na potrzebę testu, niezbędnych do poprawnych obliczeń (tutaj, lista ładunków do wysłania)).

Następnie wywołujemy metodę klasy testowanej w sekcji when:.

W sekcji then: sprawdzamy, czy wywołanie zależności zamokowanych miało miejsce
(1 *  znaczy, że metoda wywołana została dokładnie jeden raz). Dodatkowo wywołanie repozytorium:

1 * repository.findByCargoTypeAndOwner() >> knownCargos

spowoduje przesłanie na wyjscie przygotowanej listy znanych ładunków, które są niezbędne do działania metody dispatchera.

W powyższym przypadku dispatchHandler() nic nie zwraca. Gdy jednak byłoby inaczej, należałoby taka odpowiedz zapisać np:.

def result =  dispatchHandler.handleCargoDispatch()

i w sekcji then: wykonać asercję tego wyniku.

Podsumowanie

To tyle tytułem wstępu do pracy ze spock’iem. Mam nadzieję, że infromacje z tego i poprzedniego wpisu pozwolą Wam na rozpoczęcie przygody z testowaniem w Spock’u.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *