[Java] Przetestowanie metody zwracającej void

0

Cześć
Jak przetestować poniższą metodę w klasie UserService:

public class UserService {
...
    public void updateEntity(User user) {
        repositoryDAO.save(user);
    }
}

Używam mockito, junit 5, jpa

1

Oprócz sprawdzenia czy było uderzenie w metodę .save() zbyt wiele nie przetestujesz :/
Mockito.verify(repositoryDAO, times(1)).save(any());

0

OK. A czy nie można stworzyć sobie jakiegoś obiektu User i wykonać

 repositoryDAO.save(user)

i potem

assertEquals("Jan", service.findById(1).getName());
2
biurostron napisał(a):

OK. A czy nie można stworzyć sobie jakiegoś obiektu User i wykonać

 repositoryDAO.save(user)

i potem

assertEquals("Jan", service.findById(1).getName());

To zależy czy robisz test jednostkowy, czy integracyjny ;)

6

Ta metoda nie ma żadnej logiki więc nie ma sensu jej testować. Chyba że chcesz podwyższyć procent pokrycia kodu. To wtedy czasem nawet do geterów i seterów pisze się testy

0

Z reguły uważam że metody pracujące na bazie nie powinny być void. Tym bardziej .save() który przecież już zwraca zapisywaną encję. Dobrą praktyką po zapisie do bazy była by informacja w response entity że encja taka i taka została zapisana i umieszczenie jej w JSON'ie (lub jej częśći - jakieś zmapowane DTO).
Co do otestowania, na pewno mockito.verify() + test .updateEntity() ze zmockowanym repo.
Poza tym, metoda nazywa się update*(), przemyślałeś czy może wystąpić sytuacja z nieistniejącym id ?

@EDIT
np coś takiego

@Override
    public ForumThread update(Long id, ForumThread thread) {
        thread.setId(id);
        return forumThreadRepository
                .findById(id)
                .map(x -> thread)
                .map(forumThreadRepository::save)
                .orElseThrow(PropertyNotFoundException::new);
9

Tak czytam sobie kolejne komentarze i oczom nie wierze :) ludzie, naprawdę mockujecie repozytoria i sprawdzacie wywoływanie metod save(), update(), findById() itd?

0

@Charles_Ray: szczerze mówiąc, to tak :D

Otóż w swoich małych projektach wczytuję całą bazę do kolekcji javowych i na nich sobie pracuję. Używam Spring Data i MongoDB, które traktuję jako persistent storage.
Pisząc logikę biznesową zaczynam od testów, tych jednostkowych, szybkich, bez kontekstu springa.

Pewnego dnia, kiedy napisałem sobie logikę i zapis danych, moje testy szły na zielono, wprowadzałem sobie jakieś dane z frontu. Jakież było moje zdziwinie, kiedy okazało się, że po restarcie aplikacji danych nie ma ;)

Tak, zapisywałem dane do kolekcji w serwisie, ale zapomniałem wywołać save na repozytorium. Z tego powodu napisałem sobie dwie abstrakcyjne klasy jak DataProvider i DataService, coś takiego:

abstract class DataProvider<T : DataDomain>(
        private val emptyList: Boolean = false
) : CrudRepository<T, String> {

    private var items: List<T>

    init {
        items = if (emptyList) List.empty() else sampleData()
    }

    open fun sampleData(): List<T> {
        return List.empty()
    }

    override fun <S : T?> save(p0: S): S {
        this.items
                .find { items -> items.id == p0?.id }
                .map { oldItem ->
                    this.items = this.items
                            .remove(oldItem)
                            .append(p0)
                }
                .getOrElse { this.items = this.items.append(p0) }
        return p0
    }

    override fun findAll(): MutableIterable<T> {
        return this.items
    }

//.... pozostale metody

abstract class DataService<T : DataDomain>(
        val repository: CrudRepository<T, String>
) {
    private var items: List<T> = List.ofAll(repository.findAll())

//.... podobne metody jak w DataProvider

W DataService mam podobne metody jak w DataProvider, tylko np. metoda addOrUpdate zapisuje dane zarowno do kolekcji items jak tez robi save na repository. No i to mam przetestowane raz. W ten sposób wiem, że jak wywołuję addOrUpdate w serwisie pochodnym od DataService to save idzie i do kolekcji, i do bazy danych.

I tak wiem, że jak będzie jakiś błąd z serializacją, albo coś ze Spring Data czy samą bazą, to moje testy i tak będą szły na zielono. Do sprawdzenia tego potrzebne są testy integracyjne z całym kontekstem. Mimo to i tak uważam, że takie sprawdzanie czy jest save na repository jest lepsze niż gdyby tego nie było w ogóle.

2
kkojot napisał(a):

I tak wiem, że jak będzie jakiś błąd z serializacją, albo coś ze Spring Data czy samą bazą, to moje testy i tak będą szły na zielono. Do sprawdzenia tego potrzebne są testy integracyjne z całym kontekstem. Mimo to i tak uważam, że takie sprawdzanie czy jest save na repository jest lepsze niż gdyby tego nie było w ogóle.

Takie testy mają ten problem, że zagłębiają się w wewnętrzną logikę funkcji przez co uniemożliwiają w przyszłości prosty refaktor.

Przykładowo, ktoś postanawia wrzucać Mockito gdzie popadnie i sprawdzać czy konkretne metody się wywołały. Potem ten sam ktoś chce zmienić kilka funkcji (różne powody - zmiana biznesowa, bo można to ogarnąć lepiej itd.) i nagle się okazuje, że ma do poprawienia 200 testów. Taki sposób testowania bardziej przeszkadza niż pomaga i prowadzi do systemu w którym nic nie chcesz zmieniać (bo się nie da).

Może Mockito gdzieś ma jakiś sens (nie udało mi się go jeszcze spotkać, poza mega legacy kodem) ale za każdym razem jak się go chce użyć w nowym kodzie to najpierw zastanowiłbym się dlaczego w ogóle istnieje taka potrzeba. Jest duża szansa, że problemem jest kod, który jest napisany tak, że nie da się go prosto otestować, a nie realna potrzeba użycia Mockito. Może metody robią za dużo, może podział odpowiedzialności jest pomieszany pomiędzy klasami, a może została wybrana zbyt mała jednostka testowa, powodów może być wiele.

0

Cieżko mi jest komentować Mockito, bo nigdy z tego nie korzystałem. Ale korzystam ze Spring Data, bo działa to całkiem nieźle i tak mi jest wygodnie. I z racji tego, że to repozytorium wstrzykuje mi Spring, do testów jednostkowych potrzebuję, no cóż.. mocka, imitację działania tego repozytorium. Stąd takie klasy jak DataProvider i DataService, które opisałem powyżej.

I jeśli mam 200 serwisów, które dziedziczą po DataService, to wywołanie metody save czy update w repository mam przetestowane tylko w jednym miejscu - w teście klasy abstrakcyjnej DataService. Dlatego problem refaktoringu opdada. Problemem jest tylko to, że testuję swojego mocka (choć w testach to jawnie widzę, że wstrzykuję sobie DataProvider), dlatego nie wykluczam potrzeby pisania testów integracyjnych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0