Technologie .NET - laboratorium 5
Post

Technologie .NET - laboratorium 5

Wstęp i opis

Bieżący skrypt zawiera instrukcje implementacji kontrolerów dla REST API, obsługą błędów aplikacji, testów jednostkowych oraz instrukcje uruchomienia serwisu.

Kontrolery

Kontrolery w aplikacjach Rest’owych służą do definiowania oraz obsługi punktów końcowych - endpointów, których zadaniem jest odbieranie żądań dotyczących określonego zasobu na serwerze.

LandLordController

Stwórzmy kontroler do obsługi żądań pokrywających funkcjonalności w obrębie osoby wynajmującej. W katalogu Controllers w głównym projekcie solucji, dodajmy nową klasę LandLordController. Klasa ta powinna dziedziczyć po klasie ControllerBase , która zapewnia właściwości do obsługi żądań HTTP. Nagłówek klasy obarczmy jeszcze dwoma atrybutami: [ApiController] oraz [Route(“api/[controller]”)]. Pierwszy z nich wskazuje środowisku że bieżący typ klasy oraz typy pochodne są używane do obsługi odpowiedzi HTTP. Drugi natomiast określa ścieżkę do kontrolera - w tym wypadku będzie to “api/landlord”. Przejdźmy do implementacji endpoint’a, który będzie odpowiedzialny za dodawanie nowego konta osoby. Dodajmy publiczną asynchroniczną metodę CreateNewLandLordAccount która w argumentach aplikacji będzie przyjmowała klasę LandLordCreationRequestDto z atrybutem [FromBody] , który dwskazuje że otrzymane dane z zapytania przychodzą z body http. Metoda powinna zwracać typ Task<IActionResult>, który pozwala na otrzymywanie odpowiedzi wraz z odpowiednimi kodami statusu HTTP. Do funkcji musimy jeszcze dodać nagłówek z atrybutem [HttpPost(“Create”)] , który definiuje nam typ komunikacji oraz ścieżkę do endpoint’a - Create. Na poprzednich laboratoriach stworzyliśmy już logikę do tworzenia nowego konta osoby wynajmującej. Wystarczy że tylko ją wykorzystamy w stworzonej metodzie. Jeśli akcja zapisu nowego konta się powiedzie, to powinniśmy zwrócić odpowiedni kod HTTP - 202. Dlatego dodajmy return, który zdefiniuje ten kod - return NoContent(); Cała stworzony kontroler powinien wyglądać jak na poniższym zrzucie ekranu:

Desktop View

ApartmentController

Tak jak w poprzednim punkcie, stworzymy nowy kontroler działający w obrębie operacji wykonywanych nad apartamentem. Dodajmy ApartmentController. Kontroler powinien zawierać trzy metody - do dodawania nowego apartamentu do konta wynajmującego, zwracania wszystkich apartamentów oraz do zwrócenia najtańszego apartamentu. Metody które tylko zwracają dane, powinny używać metody Http - GET. Zwróćmy uwagę że nasza implementacja repozytorium jeśli nie znajdzie danych osoby wynajmującej z zapytania, rzuca wyjątkiem. Wykorzystamy ten wyjątek by zwrócić użytkownikowi odpowiedni kod błędu - 400 BadRequest. Endpointy które będą zwracały dane powinny z kolei wykorzystać kod 200 - Ok. Domyślnie .Net 6 serializuje I deserializuje żądania i odpowiedzi w formacie JSON we wbudowanej bibliotece, dlatego też nie potrzebujemy żadnej dodatkowej konfiguracji. Całość kontrolera powinna wyglądać tak jak na zrzucie ekranu niżej:

Desktop View

Zadania:

  1. Przetestuj działanie stworzonych endpointów. Dodaj nowe konto osoby wynajmującej, przypisz do niego lokal i zwróć dane wszystkich apartamentów.

Logowanie błędów

W tym punkcie pokryjemy temat prostego logowania błędów oraz informacji do konsoli. Aby tworzyć logi wystarczy użyć interfejs ILogger<Implementacja> i dodać jego inicjalizację poprzez konstruktor (obiekt zostanie stworzony poprzez wstrzykiwanie zależności bez żadnej dodatkowej konfiguracji). Dodajmy logowanie błędu w momencie w którym kontekst bazodanowy nie znajduje osoby wynajmującej po zadanym identyfikatorze i rzuca wyjątek. Przed wywołaniem wyjątku zalogujmy błąd, który zwróci nam jaki nieprawidłowy identyfikator został podany:

1
logger.LogError(Cannot find landlord with provider id: {LandLordId}, id);

Desktop View Desktop View

Przetestujmy działanie. Wyślijmy nieprawidłowe zapytanie poprzez stworzone wcześniej API tworzenia nowego lokalu. Zalogowany błąd w konsoli powinien wyglądać podobnie:

Desktop View

Testy jednostkowe

Przejdźmy do implementacji testów jednostkowych. Na pierwszych laboratoriach stworzyliśmy projekt ApartmentRental.Tests i dodaliśmy do niego bibliotekę xUnit. Biblioteka ta jest darmowym, open-sourcowym narzędziem do pisania testów jednostkowych w .Net. Oprócz tej biblioteki skorzystamy jeszcze z narzędzia FluentAssertions , które jest obszernym zestawem metod rozszerzających, które pozwalają na naturalniejsze określanie wyników testów jednostkowych. Poprzez Nugget packages dodajmy do projektu z testami wspomnianą bibliotekę.

Desktop View

Dodatkowo potrzebna nam będzie biblioteka do mockowania obiektów. W ten sam sposób dodajmy pakiet Moq.

Desktop View

Zaimplementujmy testy jednostkowe dla funkcjonalności która zwraca apartament o najmniejszym czynszu z klasy ApartmentService. W projekcie dodajmy nową klasę ApartmentServiceTests.

Pierwszym testem jednostkowym który napiszemy, będzie test sprawdzający czy metoda zwróci null, w momencie kiedy kolekcja apartamentów nic nie zwróci. Dodajmy asynchroniczna metodę:

1
public async Task GetTheCheapestApartmentAsync_ShouldReturnNull_WhenApartmentsCollectionIsNull()

Dodajmy powyżej tej metody atrybut [Fact] , który definiuje że jest ona testem. W metodzie zainicjalizujmy nasz System Under Test - ApartmentService. Jako że w testach jednostkowych nie testujemy samej bazy danych, to wszystkie repozytoria oraz serwisy które nie są nam potrzebne dla danego testu (za pomocą biblioteki Moq ), możemy zamockować. Stwórzmy zmienną sut , która tworzy obiekt AparmentService , a jako argumenty konstruktora przekażmy wspomniane Mocki. Moq pozwala stworzyć proste obiekty za pomocą struktury Mock.Of<Interfejs>(). Dodajmy teraz zmienna która przechowywać będzie nasz rezultat testu - result , która wywołuje testowaną metodę GetTheCheapestApartmentAsync(). ]Za pomocą FluentAssertions sprawdzimy wynik testu. Biblioteka ta powoduje że każdy obiekt jest rozszerzony o możliwe zagadnienia testowe. W naszym wypadku wystarczy wywołać metodę Should() i BeNull() z obiektu result.

Zaimplementowany test powinien prezentować się następująco:

Desktop View

Stwórzmy test, który teraz zwróci nam faktyczny najtańszy lokal. Do klasy z testami dodajmy następną metodę:

1
public async Task GetTheCheapestApartmentAsync_ShouldReturnTheCheapestApartment()

Tak jak zostało wspomniane wcześniej, w testach jednostkowych nie otrzymamy danych z bazy, więc te dane musimy sobie “zamockować”. Stwórzmy najpierw kolekcję apartamentów i dodajmy w niej kilka wypełnionych obiektów typy Apartment:

Desktop View

Teraz musimy stworzyć mock naszego repozytorium i wskazać by metoda GetAllAsync, zwracała naszą stworzoną kolekcję apartamentów. W tym celu dodajmy obiekt:

1
apartmentRepositoryMock = new Moq.Mock<IApartmentRepository>();

Taka inicjalizacja, pozwala nam na odpowiednie przygotowanie zachowania danego obiektu. Wywołajmy metodę Setup() , która przyjmująca delegatę funkcji GetAllAsync(). Następnie po tej metodzie należy wywołać RetunsAsync(), która przyjmie jako argument naszą listę. W ten sposób skonfigurowaliśmy mock funkcji GetAllAsync i za każdym razem kiedy testowany obiekt będzie wywoływał ApartmentRepository i tą metodę, to dostanie w zwrocie naszą kolekcję. Tak jak poprzednio stwórzmy zmienną sut , z tą różnicą że teraz konstruktor przyjmie obiekt naszego mocka.

Desktop View

Dodajmy zmienną result , wywołująca naszą testowaną metodę i za pomocą rozszerzeń FluentAssertions sprawdźmy czy każda właściwość którą zwróciła nam testowana metoda spełnia założenia testu.

Desktop View

Uruchamiając nasze testy, możemy zobaczyć że wykonały się one pomyślnie:

Desktop View

Zadanie:

  1. Dodaj kilka testów (miniumum dwa) testujące jakąkolwiek inną funkcjonalność aplikacji