Refleksja w testach

Dziś krótko chciałbym przedstawić jaki problem niosą ze sobą pewne metody wykorzystywane często w testach. Dziś piszemy w java 🙂

Refleksja

Gdyby zgłębiać szczegóły refleksja może brzmieć tajemniczo. Na chłopski rozum to po prostu metoda która sprawia że hermetyzacja nie istnieje i dzięki różnym tajemniczym operacjom mamy dostęp do prywatnych pól. Możemy też odczytać wszelkie metody jakie dana klasa zawiera, konstruktory itd. - taki backdoor i wciskanie różnych rzeczy na siłę. Jak tę różową piłeczkę przez siatkę.

Dlaczego w testach?

Jeśli piszemy źle, albo jeśli zastaliśmy zły kod to zdarza się że nie da się go przetestować po bożemu. Taki spring by default pozwala na wstrzykiwanie zależności do prywatnych pól zamiast przez konstruktor i często spotykamy się z tego typu kodem. Czasem są sytuacje że inaczej się nie da - jak np. w poprzednim poście kiedy beany mają różne scopy. No i mamy mockito które spieszy z pomocą z adnotacją @Mock i @InjectMock - czasami jednak to nie wystarcza i musimy wstrzyknąć konkretną instancję (@Spy), albo też nie możemy jej zainicjować w sekcji @Before testu i instancje obiektów wewnątrz obiektu i w scopie testu różnią się. Wtedy spotkamy się z innym zapisem tzw. "na chama".
1
ReflectionTestUtils.setField(toFill, "mysteriousNameOfField", toInject);
Nie wiem czy tylko ja mam problem z tym że podajemy w stringu nazwę pola, ale jakoś nikomu to nie przeszkadza. Jeśli mamy wiele pól o tym samym typie to chyba nie ma innej możliwości, ale przy jednym polu danego typu? Stwierdziłem że da się to prosto zrobić. I oto mój util:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static <T> T injectPrivateDependency(T toFill, Object toInject) {
        Class<?> toFillClass = toFill.getClass();
        // ReflectionTestUtils.setField(toFill, "mysteriousNameOfField", toInject);
        Optional<Field> chap = Arrays.stream(toFillClass.getDeclaredFields())
                .filter(x -> x.getGenericType().getTypeName().equals(toInject.getClass().getTypeName())).findFirst();
        chap.ifPresent(x -> {
            try {
                x.setAccessible(true);
                x.set(toFill, toInject);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        return toFill;
    }
W ramach wyjaśnień. Bierzemy klasę obiektu którą chcemy wypełnić i szukamy w niej pola które ma typ obiektu do wstrzyknięcia. Bierzemy pierwszą znajdźkę i jeśli takowa się znalazła - ustawiamy obiekt, zwracając wypełniony obiekt.

Testy oraz sam kod znajdziesz tutaj: github logo

Close Menu