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:
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:
Zadania:
- 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);
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:
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ę.
Dodatkowo potrzebna nam będzie biblioteka do mockowania obiektów. W ten sam sposób dodajmy pakiet Moq
.
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:
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
:
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.
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.
Uruchamiając nasze testy, możemy zobaczyć że wykonały się one pomyślnie:
Zadanie:
- Dodaj kilka testów (miniumum dwa) testujące jakąkolwiek inną funkcjonalność aplikacji