Unit testy w angularze.

0

Tematów trochę jest na internecie odnośnie pisania Unit Testów w Angularze, ale nie znalazłem odpowiedzi na swoje pytania. Uczę się pisać testy jednostkowe w Angularze (8, nie AngularJs). Tylko problem widzę taki, że niektórych klas nie widzę sensu testowania. Odnośnie testowania:

  • Pipe'ów, to ok można to testować bo jakąś logikę to ma i tak też robię.
  • Serwisów, tutaj nie do końca rozumiem testowanie. Bo, jeżeli serwis odpowiada za dostarczenie danych (najczęściej httpClientem z serwera) to testowanie takiego serwisu raczej nie ma sensu. Nawet w sytuacji, gdy wynik jest rxjs.pipe() to i tak jak się zmockuje httpClienta korzystając z HttpTestingModule to i tak ten pipe jest pomijany. Testowanie serwisów, które jakąś logikę biznesową wykonują (Serwisów-Fabryk, Serwisów-Walidatorów, itd) to to jak najbardziej można testować.

Natomiast nie wiem co z dyrektywami oraz komponentami. Czy testując komponenty, testuje się też tylko ich metody czy wygenerowany widok? Problem z testowaniem komponentów jest też taki, że żeby przetestować jedną metodę w komponencie, trzeba odpowiednio test skonfigurować (declarations, imports, providers) a następnie wszystkie pola inicjować które są wykorzystywane w ngOnInit (żeby jakiegoś nulla/undefined nie dostać). Mógłbym prosić o rady jak dobrze testować w angularze?

0

Pozwolę sobie bezczelnie zalinkować 4 posty z bloga firmowego odpowiadające na Twoje pytania:
https://blog.consdata.tech/2019/11/06/testowanie-frontendu-wprowadzenie-do-jasmine.html
https://blog.consdata.tech/2019/11/20/testowanie-komponentow-i-serwisow.html
https://blog.consdata.tech/2019/12/04/testowanie-komponentow-z-inputami-i-outputami.html
https://blog.consdata.tech/2019/12/19/testowanie-frontendu-asynchronicznego.html

Testowanie widoków nie ma za bardzo sensu, bo jest to element kodu, który może ulegać częstym zmianom i przy każdej takiej zmianie musiałbyś prawdopodobnie zmieniać testy. Dodatkowa, bezsensowna robota.
Testowanie czy coś Ci dobrze wraca z HttpClienta - moim zdaniem średnio ma sens. Trzeba pomyśleć co się z tymi danymi dzieje, bo jeśli niewiele, to test się sprowadzi do sprawdzenia, czy działa HTTP - a to raczej wiemy :D Na ogół jak piszę kodzik w Angularze to wydzielam sobie 3 pliki na każdy "niegłupi" komponent - component.ts, controller.ts i service.ts. W pierwszym trzymam cały widok i metody wywołujące serwis. W serwisie wywołuję kontroler i ogarniam logikę biznesową aplikacji. W kontrolerze operuję wszystkim, co komunikuje się ze światem zewnętrznym, np. requesty HTTP. Sam nie jestem pewien, czy to jest dobry podział, ale pozwala napisać sensowne testy - wyłącznie do serwisu. Reszta, pod względem testowania, mnie nie obchodzi.

0

Trochę mi rozjaśniło to sytuację i sam się zastanawiałem nad podobnym podejściem. Takie podejście pozwoliłoby mi kompletnie zignorować Componenty podczas testowania, bowiem cała logika siędziałaby w serwisach. Problem tylko jest taki, że jeżeli serwis woła kontroler to podczas pobierania danych z HttpClienta, trzeba albo jakieś osobne serwisy robić które by powiadamiały w przypadku gdy response nadejdzie (czy to z obiektem czy z errorem). Z drugiej strony, jeśli jakaś akcja może zmienić kilka rzeczy w komponencie, to potem mogą się rodzić metody z kilkoma argumentami nie powiązanymi ze sobą. Myślałem też o tym, żeby zrobić to tak jak w MVC, czyli Komponent ma wstrzyknięty kontroler i on odpowiada za komunikację z serwisem. Tylko znów problem jest taki, że jeżeli będzie potrzebne wywołanie jakiejś logiki, to znów będą dość dzikie metody. To mi nasunęło myśl, żeby Componenty miały po dwa serwisy - jeden pełnił rolę kontrolera, drugi posiadał całą logikę komponentu. Tylko to też nie rozwiązuje problemu z dzikimi metodami - no chyba, że można napisać Componenty tak żeby się tego pozbyć, a na razie jeszcze tego nie umiem :D

0

Dlaczego trzeba by osobny serwis robić? W serwisie masz subscribe na to, co przychodzi z kontrolera i przekazujesz to do komponentu. Serwis jest wstrzyknięty w konstruktor komponentu.

0

No tak, tylko z racji tego, że Http Requesty są asynchroniczne, nie jesteś wstanie przekazać tego w returnie. Więc jedyne co przychodzi mi do głowy, to kolejny Subject/BehaviourSubject -> Observable który byłby subskrybowany w komponencie, co dla mnie jest trochę bezsensu. No chyba, że jest jakaś inna opcja, na którą nie wpadłem jeszcze albo inaczej wyobrażam sobie hierarchię klas którą napisałeś:
Komponent <- Serwis_Logika <- Serwis_Http, gdzie "<-" oznacza wstrzyknięcie.Dwa serwisy wstrzyknięte do komponentu są dla mnie o tyle prostsze, że:

  1. Odpada testowanie komponentów, bo te nie mają żadnej logiki.
  2. Odpada testowanie i mokowanie Http Serwisów, bo te tylko dostarczają dane.

Tym się to różni od Twojego podejścia, że u Ciebie Serwis odpowiedzialny za dostarczenie danych jest wstrzyknięty do kolejnego serwisu, u mnie raczej by to wyglądało że oba serwisy nie komunikują się ze sobą i są wstrzyknięte do komponentu.

Edit: Przeczytałem to: https://angular-academy.com/angular-architecture-best-practices/ i wydaje mi się, że o podobnej architekturze myślicie. Tylko patrząc na przykłady jego Komponentów i Serwisów, wstrzykuje Serwis który dostarcza logikę do Komponentu CategoriesComponent z @Input na jakiejś liście. Czyli wnioskuję, że ten komponent jest osadzony wewnątrz innego komponentu który ma za zadanie pobrać dane. Czyli tutaj oprócz rozdzielenia serwisu który dostarcza dane oraz serwisu odpowiedzialnego za logikę, jest jeszcze dodatkowy podział komponentów: pierwszy dostarcza dane, drugi @Inputem je odbiera i odpowaida za logikę (za pomocą serwisu) a oprócz tego są jeszcze presentational componenty które mają dodatkowo tylko wyświetlić dane (np CategoryListComponent, wewnątrz ma ngFor gdzie wstrzykuje do CategoryComponent obiekt z tablicy). I o ile jestem wstanie zrozumieć komunikację Subjectami/BehaviourSubjectami i Observables, o tyle ciężko mi zrozumieć jak ma być przekazywana informacja o błędzie, to rola komponentu jest wyświetlić tą informację. Czy to w postaci Toasta czy informacji tekstowej. Tylko tutaj też jest kolejna przeszkoda w postaci takiej, że jeżeli operuje się na czymś w postaci tablicy to mogą wystąpić problemy na dwóch płaszczyznach: pobrania jej oraz operacji na danych w niej zawartych. Nie jestem wstanie sobie tego wyobrazić, jak w takim modelu miałaby wyglądać komunikacja z API i respone handling.

3

**Kontroler: ** rzuca POSTami, GETami, PUTami, czym chcesz i przekazuje odpowiedź as is do serwisu. Nic z nią nie robi, jego odpowiedzialnością jest tylko poprawne uderzenie pod API i przekazanie odpowiedzi do serwisu, nawet jeśli w odpowiedzi jest błąd.
**Serwis: ** odbiera odpowiedź od kontrolera i odpowiednio ją interpretuje. Na tym poziomie odbywa się cały response handling. Odpowiednio obsłużone dane przekazuje do komponentu. Często trzyma również stan komponentu - jest jedynym źródłem prawdy dla widoku.
**Komponent: ** jest jak najbardziej głupi, robi tylko to, co mu powie serwis, nie ma w nim żadnej logiki. Jeśli z serwisu przyjdzie obsłużony błąd - wyświetl coś użytkownikowi. Jeśli przyjdą dane - wyświetl je. Cokolwiek, ale bez logiki biznesowej.

Tak to u mnie wygląda. W podejściu, gdzie chciałbyś mieć 2 serwisy niekomunikujące się ze sobą miałbyś 2 źródła prawdy (source of truth), nie bardzo widzę zalety takiego podejścia i nie rozumiem Twoich punktów. Ja również nie testuję komponentów w moim podejściu, tak samo nie testuję kontrolerów. Tylko serwisy

0

Dobra, chyba zrozumiałem. Czyli w takim przypadku mniej więcej tak ma wyglądać komunikacja między serwisami a Komponentem? https://github.com/KamLar/AngularArchitecture/tree/master/src/app/absence-list
Komponent teraz jest jak najbardziej informowany o zmianie stanu wewnątrz serwisu, jednocześnie w 100% odwzorowując jego stan u siebie (wymuszone BehaviourSubjectami). Oczywiście pominąłem tutaj fakt, że trzeba unsubscribe w onDestroy, oraz podobnie jak w AbsencesList, tak samo powinno wyglądać to w DatePicker, tylko bez ApiService. Tam też w ngOnInit dodany zostałby subscirbe na to co w FacadeService by było. Jeżeli dodany byłby jakiś mechanizm wyboru dat, to po prostu nastąpiłoby powiadomienie Komponentu i Emit nowej wartości. Tak?

0

Tak, jeśli pisać wg tych wytycznych, to jest spoko :) Dobrze, że się zrozumieliśmy :D

0

Okej dzięki wielkie za pomoc. A jeszcze jedna sprawa odnośnie tego. Takie podejście w którym przechowywanie stanu komponentu jest w Serwisie, powoduje przynajmniej dwa problemy. Po pierwsze, w przypadku gdy komponent zostanie zniszczony, oraz ponownie utworzony, jego stan nie zostanie zresetowany. Drugi problem jest z tym związany, gdy będą dwa lub więcej komponenty istniały w jednym czasie. Wydaje mi się, że dla komponentu logiki-trzymającego stan, powinnna być inna "strategia" injectowania (nie providedIn: 'root' tylko taka, żeby za każdym razem został tworzony nowy serwis). Dobrze myślę?

0

Zależy od komponentu. Jeśli masz jeden, duży komponent, np. całą stronę, agregującą wiele innych, mniejszych komponentów, to jej stan raczej warto trzymać w serwisie. Dla małych, "głupich" komponentów trzeba się zastanowić, czy w ogóle jest sens pisać serwis kontroler i czy np. nie przekazać im wszystkiego, co muszą wiedzieć przez @Input.

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