Mockito, a dependency injection

Mockito, a dependency injection
0

Witam,
Mam problem w jaki sposób mockować/wskazać mockowi, że ma wykonać prawdziwą metodę, która jest dostarczana za pomocą dependency injection (np. za pomocą adnotacji @ejb lub @Inject).

Oczywiście EJB nie jest tutaj istotną kwestią (transakcje nie mają znaczenia: liczy się mockowanie, gdzie występuje dependency injection). Celem jest wytestowanie logiki biznesowej, nie transakcji itp.

Kopiuj
@Stateless
public class TaskService implements TaskServiceLocal {
    
    @EJB
    private TaskFacadeLocal taskDao;

    @Override
    public boolean taskExists(long id) {
        Task idTask = taskDao.find(id);
        if (idTask == null) {
            return false;
        } else {
            return true;
        }
    }

}
Kopiuj
@Stateless
public class TaskFacade extends AbstractFacade<Task> implements TaskFacadeLocal {
    
    @PersistenceContext(unitName = "taskList-PU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public TaskFacade() {
        super(Task.class);
    }

}
Kopiuj
public abstract class AbstractFacade<T> {
    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

}

Problem: w jaki sposób dostarczyć mock TaskFacadeLocal do środka mocka TaskService?

W teście brakuje mocka na TaskFacadeLocal

Kopiuj
public class TaskServiceTest {

    private static final Long RET_TASK_ID = 1L;

    @Test
    public void testTaskExists() throws Exception {
        TaskService taskSrvUnderTest = mock(TaskService.class);
        TaskFacade taskFacadeUnderTest = mock(TaskFacade.class);
        Task a = new Task();
        a.setTaskId(RET_TASK_ID);
        when(taskSrvUnderTest.taskExists(RET_TASK_ID)).thenCallRealMethod();
        when(taskFacadeUnderTest.find(a)).thenReturn(a);
        assertTrue(taskSrvUnderTest.taskExists(RET_TASK_ID));
        verify(taskSrvUnderTest).taskExists(RET_TASK_ID);
    }

}

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
0

Źle to robię i mało rozumiem. Każda wskazówka będzie mile widziana, bo mam w głowie mętlik.

Kopiuj
@RunWith(MockitoJUnitRunner.class)
public class TaskServiceTest {

    private static final Long RET_TASK_ID = 1L;
    
    @InjectMocks
    private TaskService taskSrvUnderTest = new TaskService();
    @Mock
    private TaskFacadeLocal taskFacade;
    
    @Test
    public void testTaskExists() throws Exception {
        Task a = new Task();
        a.setTaskId(RET_TASK_ID);
        when(taskFacade.find(a)).thenReturn(a);
        assertTrue(taskSrvUnderTest.taskExists(RET_TASK_ID));
        verify(taskSrvUnderTest).taskExists(RET_TASK_ID);
    }
}

Test wykłada się na assercie tzn. owszem jakiś (raczej nie mój) mock został wstrzyknięty (bo nie ma NullPointerException), jednak zwraca domyślną wartość. Dla @InjectMocks podaje prawdziwy obiekt (nie rozumiem dlaczego), a nie @Mock (na którym mógłbym wykonać callRealMethod).

Zmodyfikowałem też klasę TaskService (w praktyce nie chcę modyfikować klas biznesowych, aby móc testować, ale najpierw chcę zrobić tak, aby zadziałało):

Kopiuj
@Stateless
public class TaskService implements TaskServiceLocal {

    @EJB
    private TaskFacadeLocal taskDao;

    @Override
    public boolean taskExists(long id) {
        Task idTask = taskDao.find(id);
        if (idTask == null) {
            return false;
        } else {
            return true;
        }
    }

    /*
     * setters
     */
    public void setTaskDao(TaskFacadeLocal taskDao) {
        this.taskDao = taskDao;
    }
    
}

Pisząc: 'Dla @InjectMocks podaje prawdziwy obiekt (nie rozumiem dlaczego)' miałem na myśli:

  • tworze prawdziwy obiekt (new, konstruktor) a nie mocka podając mu callRealMethod

Wciąż walczę. Co udało mi się ustalić:

Kopiuj
@RunWith(MockitoJUnitRunner.class)
public class TaskServiceTest {

    private static final Long RET_TASK_ID = 1L;

    @InjectMocks
    private TaskService taskSrvUnderTest;
    @Mock
    private TaskFacadeLocal taskDao;

    @Test
    public void testTaskExists() throws Exception {
        Task a = new Task();
        a.setTaskId(RET_TASK_ID);
        when(taskDao.find(a)).thenReturn(a);
        assertTrue(taskSrvUnderTest.taskExists(RET_TASK_ID));
        verify(taskSrvUnderTest).taskExists(RET_TASK_ID);
    }
}

  1. Na debugerze ustaliłem, że taskDao wstrzykuje się do taskSrvUnderTest.
  2. Dodatkowy setter nie jest do tego celu potrzebny.
  3. Podczas egzekucji metody testu, wstrzyknięty, nienullowy mock zwraca mi null (wartość domyślną) zamiast obiektu a:
Kopiuj
assertTrue(taskSrvUnderTest.taskExists(RET_TASK_ID));

Wykonywana metoda (idTask dostaje null zamiast obiekt a):

Kopiuj
    public boolean taskExists(long id) {
        Task idTask = taskDao.find(id);
        if (idTask == null) {
            return false;
        } else {
            return true;
        }
    }
edytowany 1x, ostatnio: Koziołek
airborn
  • Rejestracja:prawie 16 lat
  • Ostatnio:prawie 7 lat
  • Postów:274
0

Problem w tym, że Twoja metoda find w AbstractFacade przyjmuje argument typu Object, w teście spodziewasz się, że zostanie ona wywołana z argumentem typu Task (when(taskDao.find(a)).thenReturn(a);) kiedy tak na prawdę wywołujesz ją z Longiem (taskDao.find(id);).

Powinno zadziałać, jeżeli zamockujesz to w ten sposób:
when(taskDao.find(RET_TASK_ID)).thenReturn(a);

P.S.
Ten verify tam jest zupełnie bez sensu.

0
airborn napisał(a):

Ten verify tam jest zupełnie bez sensu.

Moim zdaniem caly test jest zupelnie bez sensu. W pierwszym poscie mockuje i TaskFacade i TaskService, i chce testowac TaskService.
Ogolnie to juz ktorys temat w ostatnim czasie gdzie ludzie robia takie rzeczy. Nie czaje, rozumiem ze moze testowanie jest na czasie i mockowanie rowniez, ale powinno sie przede wszystkim zrozumiec co sie chce testowac. W tym przypadku testowany jest kod biblioteki do mockowania. Wszystko jest zle. Costam swita ze chce wolac prawdziwa metode (callRealMethod) ale nie czai ze to zupelnie niepotrzebne itp.

Jesli chcesz testowac TaskService (tak wynika z nazwy testu), to znaczy ze chcesz testowac twoj wlasny kod. Aby moc latwo testowac TaskService, musisz w jakis sposob tak podstawic cos do pola TaskFacadeLocal, zeby robilo to co ty chcesz. Mozesz napisac wlasne klasy ktore sa podklasami TaskFacadeLocal i podstawic w to pole, albo wlasnie stworzyc mocka i nawtykac mu funkcjonalnosci za pomoca when itp. Nastepnie, po tescie, w fazie weryfikacji, sprawdzasz czy winiki dostajesz takie jak chcesz oraz mozesz weryfikowac czy metody mocka zostaly tak wywolane, tyle razy, i z takimi parametrami jak oczekiwales. Nie zawsze jest to potrzebne. Nigdy nie powinienes mockowac klasy ktora chcesz testowac, tylko jej zaleznosci. W twoim przypadku bedzie ciezko cos podstawic do taskDao w TaskService bo uzywasz field injection. Zmien na setter injection albo constructor injection, i w tescie wywolaj ta metode / konstruktor. Cos takiego:

Kopiuj
@Stateless
public class TaskService implements TaskServiceLocal {
    private TaskFacadeLocal taskDao;

    @Inject
    public TaskService(TaskFacadeLocal taskDao) {
        this.taskDao = taskDao;
    }

    @Override
    public boolean taskExists(long id) {
        return taskDao.find(id) != null;
    }
}

public class TaskServiceTest {
 
    @Test
    public void testTaskExists() {
        long taskId = 1L;

        // konfiguracja mockow
        TaskFacade taskFacade = mock(TaskFacadeLocal.class);
        Task a = new Task();
        // mockujesz szukanie Task po id
        when(taskFacade.find(taskId)).thenReturn(a);

        // tworzenie prawdziwego SUT (system under test) z mockiem
        TaskService testServiceUnderTest = new TasService(taskFacade);

        // wolanie kodu ktory chcemy testowac
        bool exists = testServiceUnderTest.taskExists(taskId);

        // weryfikacja
        assertTrue(exists);
        // moim zdaniem to jest niepotrzebne, bo oczywiste jest ze skoro test dziala to ta metoda byla wolana
        verify(taskFacade).find(taskId);
    }

    @Test
    public void testTaskDoesNotExist() {
        long taskId = 1L;

        // konfiguracja mockow
        // mocka nie trzeba konfigurowac poniewaz domyslnie metody zwracaja nulle (tak mi sie wydaje)
        TaskFacade taskFacade = mock(TaskFacadeLocal.class);

        // tworzenie prawdziwego SUT (system under test) z mockiem
        TaskService testServiceUnderTest = new TasService(taskFacade);

        // wolanie kodu ktory chcemy testowac
        bool exists = testServiceUnderTest.taskExists(taskId);

        // weryfikacja
        assertFalse(exists);
        // moim zdaniem to jest niepotrzebne, bo oczywiste jest ze skoro test dziala to ta metoda byla wolana
        verify(taskFacade).find(taskId);
    }
}

@Shalom: @InjectMocks sluzy do wstrzykiwania mockow do testu - do pol ktore maja adnotacje @mock. Nie ma to nic do rzeczy z tym co aurot chce zrobic. Jest to smutne co piszesz. Znaczy ze albo nie masz pojecia o testach i mockowaniu, albo ze nie przeczytales o co autor pyta i masz to w dupie i piszesz jakies bzdury bez zrozumienia tamatu (zakladam ze jest to bardziej pradwopodobne niz to ze nie umiesz testowac). Juz lepiej nie pisz nic jak masz dawac takie rady.

Autorowi polecam przeczytac http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html i jesli nadal nie rozumie, to jakas ksiazke o testowaniu.

Shalom
Kajam sie, ale kod przedstawiony przez autora był na tyle dziwny że zwyczajnie nie zrozumiałem o co mu chodzi ;]
0

@mućka: Bardzo doceniam przykład, który przedstawiłeś. I konstruktywną krytykę. Dzięki. Super przykład z tym wstrzyknięciem przez konstruktor (wszystko mi się rozjaśniło).

@Shalom:
Czy ten kod był taki dziwny? Ledwie jeden ifek, myślałem że jest prosty.

0

Mysle ze chodzilo o to ze te testy byly bardzo dziwne. Sam kod jest szczegolnie zly ;d

0

@mućka:
A tak w ogóle ten pomysł z używaniem callRealMethod (a więc i mocka). zamiast prawdziwego obiektu (i dostarczenia do niego zmockowanych zależności wynikał z jednej rzeczy):

  1. Często w moich klasach metody pobierające z bazy danych i logika biznesowa (która operuje na danych z bazy) przeplatały się. Dzięki temu udawało mi się je wytestować (na partial mocku). Rozumiem, że to krzywy sposób projektowania klas, bo robią się do tego dziwne testy.
  2. Widzę jedno rozwiązanie tego problemu: tworzyć dwa oddzielne service na komunikacje z bazą (DAO) i logikę biznesową (tu po prostu wstrzykuje DAO). Wtedy problem partial-mocków po prostu znika (callRealMethod nie będzie potrzebne, bo będę działać na prawdziwym obiekcie, a baza (zależność) będzie zmockowana).
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@student911 przypadkiem trafiłem na to:
http://4programmers.net/Forum/Java/238241-mockito_prosty_test_z_instrukcja_warunkowa
co mnie smuci bo oznacza że niczego się nie nauczyłeś przez te kilka miesięcy...
Przeczytaj ten temat:
http://4programmers.net/Forum/Java/238135-klopot_podczas_testow_przy_pomocy_bibloteki_mockito


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
0

@Shalom: bez urazy, ale imo czepiasz się (jednocześnie doceniam Twoje rady, jeśli nawet są kąśliwe). Zauważ proszę, że tam był zupełnie inny case: celowo przekształciłem ten sam sztuczny przykład, aby zbliżyć się do kodów, które widuje produkcyjnie. Może popełniłem błąd, że nie podlinkowałem tematu. Uznałem, ze to inny case, bo tutaj mam dependency-injection, a tam nie miałem i uznałem, że to drastycznie zmienia przetoczony wcześnie przykład.

Uznałem, że warto zapytać na forum w nowym temacie (bo to inny case z IoC) z jednego powodu: na temat testów ludzie w sieci często piszą bzdury i czasem trudno dojść do wniosku jak właściwie powinno się to robić, a tak przynajmniej ktoś kto się zna zrobi mi kod review. IMO niewiele osób potrafi testować jednostkowo.

Celowo nawiązałem do tego przykładu (ponieważ wracam do nauki Mockito, a poprzednio temat został zarzucony na callRealMethod i partial mocku, który wydawał się ok). Żyłem w błogiej nieświadomości, że partial-mocki są ok.

Kilka razy zastosowałem partial-mocki (tak jak wtedy), ponieważ klasy na to pozwalały i mogłem coś w ten sposób wytestować (z callRealMethod). Z tego co patrzę na ten przykład krzywo używałem verify. Wyciągne wniosek.

Nie przejmuje się, że mogłem nauczyć się niewiele przez ten czas:

  1. Bo robiłem wiele innych rzeczy (priorytety).
  2. Dla mnie porażka to po prostu część drogi do sukcesu.
0
Shalom napisał(a):

@student911 przypadkiem trafiłem na to:
http://4programmers.net/Forum/Java/238241-mockito_prosty_test_z_instrukcja_warunkowa

Heh, to byl wlasnie temat o ktorym pisalem ze ludzie biora sie za mocki nie rozumiejac podstaw testowania. Przyklad niemal identyczny, tyle ze tutaj costam probowano z wstrzykiwaniem (czego w tych testach wcale nie trzeba). Mam nadzieje jednak ze teraz wyciagnie z tego korzysci.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)