Pomoc z mockowaniem obiektów

0

Witam,zacząłem pisać parę bardziej skomplikowanych testów i próbuje zrozumieć jaki (czy w ogóle w tym przypadku) sens ma stosowanie Mockito.

Chce przetestować metodę, zwraca ona liczbę wierszy:


@Repository
@Transactional
public class MSSqlGameStoreDatabase implements DatabaseRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
     

    public long getNumberOfRows(){
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteria = builder.createQuery( Long.class );
        criteria.select(  builder.count( criteria.from( Game.class ) ) );
        Long value = entityManager.createQuery(criteria).getSingleResult();
        return value;
    }
    
    
}

Moja klasa testowa:

@RunWith(MockitoJUnitRunner.class)
public class MSSqlGameStoreDatabaseTest {
    @Mock
    EntityManager mockedEM;
    
    @Mock
    private CriteriaBuilder mockedCB;
    
    @Mock
    private CriteriaQuery mockedCQ;
    
    
    MSSqlGameStoreDatabase mSSqlGameStoreDatabase;
    
    public MSSqlGameStoreDatabaseTest() {
    }
    
    @BeforeClass
    public static void setUpClass() {
        
    }
    
    @AfterClass
    public static void tearDownClass() {
    }
    
    @Before
    public void setUp() {
        mSSqlGameStoreDatabase = new MSSqlGameStoreDatabase(mockedEM);
        when(mockedEM.getCriteriaBuilder()).thenReturn(mockedCB );
        when(mockedCB.createQuery(Long.class)).thenReturn(mockedCQ);
        when(mockedEM.createQuery(mockedCQ).getSingleResult() ).
                  thenReturn( mockedCQ.select( mockedCB.count( mockedCQ.from( Game.class ) )));
    }
    

    /**
     * Test of getNumberOfRows method, of class MSSqlGameStoreDatabase.
     */

    @Test
    public void testGetNumberOfRows() {
        Long value = mSSqlGameStoreDatabase.getNumberOfRows();
        assertEquals( 16, value.longValue() );
    }
    
}

Oczywiście zdaje sobie sprawę z tego, że ten test to same bzdury, ale obecnie to jest w takiej formie. Mogę prosić o odpowiedź na parę pytań?

  1. Tłumacząc łopatologicznie: metoda when działa dla linijki: "when(mockedEM.getCriteriaBuilder()).thenReturn(mockedCB );" tak:
    "*Dla zmockowanego obiektu **mockedEM **po wykonaniu metody **getCriteriaBuilder ** zwróć **mockedCB *bez względu na to co zostało faktycznie zwrócone"?
  2. Skąd mockito wie jak zrobić mock mockedEM, **mockedCB **itd.?
  3. Czemu podczas przeprowadzania testu bez mokowania (bez adnotacji "@RunWith(MockitoJUnitRunner.class)") wywala NullPointerException przy
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();?
  4. Jak poprawnie zrobić test tej motody?
  5. Czy używanie CriteriaBuilder i CriteriaQuery jest lepszym pomysłem od napisania zwykłego Query w SQL?
1

Mockito tu nie ma żadnego sensu. Klasy DAO/Repository powinno testować się integracyjnie z użyciem najlepiej kontenera IoC + bazy danych w pamięci np. H2
Criteria mają sens tylko w przypadku dynamicznych zapytań, najlepiej pisać JPQL/SQL

0

A powiedz mi ty, co konkretnie chcesz tu jednostkowo testować? Bo rozumiesz że taki test ma sens tylko żeby sprawdzić czy logika twojego kodu działa poprawnie? Tylko ze tam w zasadzie nie ma twojego kodu! Możesz niby napisać test który sprawdza czy wywołałeś criteria z odpowiednimi parametrami a potem czy odpowiednie query poszło do entity managera, ale wartość takiego testu jest w zasadzie zerowa.
W zasadzie to nawet mniej niż 0, bo każda zmiana tego kodu w twoim repozytorium powoduje konieczność zmiany testu, bo test w zasadzie testuje czy zawartość metody jest taka jak w chwili pisania testu. Więc niby mógłbym, z logicznego punktu widzenia, podmienić to wywołanie criteria na JPQL jakiś i twój test się posypie, mimo że metoda działa bez zarzutu. Widzisz chyba ze to niezbyt sensowne?

Są generalnie dwa negatywne kryteria dla testu, które go dyskwalifiują:

  1. Jeśli zmiana metody na równoważny zapis który działa poprawnie powoduje wywalenie testu to jest słaby.
  2. Jeśli popsucie metody tak że nie działa nie wywala testu to jest słaby.

U ciebie oba te warunki są spełnione.

Takie cos mozesz testować integracyjnie żeby to miało jakiś sens. Odpal sobie jakąś bazę in memory w @Before dla testu, wrzuć dane testowe a w samym teście wywołaj tą metodę i sprawdź czy dane sie pobrały.

0

Tak jeszcze mówiąc jak mockito działa.

Generalnie w momencie kiedy używasz metody mock() albo odpowiednich adnotacji mockito patrzy sobie na interfejs który tam został podany i tworzy fałszywy obiekt (bez wywołania konstruktorów, ma od tego specjalne libki) który ma takie same metody. W momencie kiedy taka metoda jest wywołana żaden kod z danej klasy nie jest wykonywany, za to wywoływany jest kod mockito czytający konfigurację i reagujący na nią. Mockować możesz nie tylko klasy ale i interfejsy (są oczywiście ograniczenia na typy klas).

Taki obiekt może być skonfigurowany przez when() żeby wykonał pewne akcje - walnął wyjątkiem, zwrócił jakąś z góry zadaną wartość. Jeśli nic nie skonfigurujesz to bodajże zwraca null. Zapamiętuje też interakcje dzięki czemu można później użyć verify().

Co do MockitoJUnitRunner to jest to rozszerzenie JUnita które skanuje testy w poszukiwaniu pól z adnotację @mock i ustawia im wartości. Generalnie adnotacje same z siebie nic nie robią, coś musi je znaleźć i na nie zareagować. Bez tej adnotacji ustawiana jest wartość domyślna - dla referencji null.

0

No tak, jak tak pomyślę to rzeczywiście @Shalom ma rację, nie ma za bardzo sensu robić takiego testu.

@Trzeźwy Młot
Dzięki!

@scibi92
Czemu akurat w przypadku zapytań dynamicznych?

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