Testowanie wysyłki email w ASP.NET MVC

Wprowadzenie

W poprzednich dwóch postach (Postal – wysyłka email w ASP.NET MVC oraz Hangfire – wysyłka email w tle) pokazałem jak wysyłać wiadomości email w aplikacji ASP.NET MVC. Jeśli nie czytałeś/czytałaś tamtym wpisów, to zachęcam do nadrobienia lektury, szczególnie, że w tym wpisie będę bazował na kodzie, z tamtych wpisów. W dzisiejszym poście chciałbym jeszcze pozostać przy tej tematyce i pokaże Ci, w jaki sposób można automatycznie testować kod odpowiedzialny za wysyłkę wiadomości w ASP.NET MVC.

Wysłanie email możemy przetestować na dwa sposoby:

  • Testy jednostkowe – sprawdzamy, czy metoda Send z IEmailService (interface pochodzi z biblioteki Postal) została wywołana z określonymi parametrami
  • Testy integracyjne – możemy skorzystać z testowego serwer SMTP, wysłać fizycznie wiadomość i później sprawdzić w serwerze jej właściwości

W tym wpisze pokaże oba te sposoby. Zachęcam to pobrania kodu przykładu z githuba – https://github.com/danielplawgo/PostalAndHangfire

Jest to ten sam przykład, w którym pokazywałem wysyłkę wiadomości za pomocą bibliotek Hangfire oraz Postal. Dodałem do niego projekt z testami.

Zmiany w kodzie

Aby móc automatycznie przetestować (w szczególności za pomocą testów jednostkowych) działanie wysyłania wiadomości email, musiałem zmienić trochę kod w klasie BaseMailer:

W poprzedniej wersji BaseMailer, w metodzie Send ręcznie była tworzona instancja klasy EmailService. W tym momencie zdecydowałem się na wprowadzenie nowego elementu, który jest odpowiedzialny, za tworzenie EmailService. Dodałem IEmailServiceFactory z jednej strony, aby łatwiej można było testować klasy „*Mailer” za pomocą testów jednostkowych oraz z drugiej strony, aby logiki tworzenie EmailService nie zamykać w konfiguracji kontenera Autofac.

Sama klasa EmailServiceFactory wygląda tak:

W EmailServiceFactory znajduje się teraz kod odpowiedzialny na konfigurację silnika Razor, aby była możliwa wysyłka wiadomości email bez kontekstu HTTP. W tym kodzie również nastąpiła drobna zmiana, aby można było testować kod.

Wcześniejsza implementacja wykorzystywała klasę HostingEnvironment oraz metodę MapPath z tej klasy do mapowania ścieżki w ramach projektu ASP.NET MVC to fizycznej lokalizacji na dysku. Niestety w przypadku uruchamiania testów (dokładnie testów integracyjnych, o których będzie później) ta metoda zwraca błąd. Dlatego trzeba ją opakować nowym interfejsem i w przypadku testów wykonać trochę inną logikę. Poniżej kod samego interfejsu oraz dwóch implementacji, jedna wykorzystywana w aplikacji, druga w testach:

Logika w TestHostingEnviromentService zmienia katalog, w którym są wyszukiwane widoku. Podczas wykonywania testów widok szukany byłby w katalogu projektu z testami, gdzie go nie ma. Dlatego w tej implementacji IHostingEnviromentService mapuje katalogu na folder właściwej aplikacji ASP.NET MVC, dzięki czemu testy integracyjne będą działać.

Testy jednostkowe

Do wykonywania testów jednostkowych oraz integracyjnych wykorzystuje bibliotekę xUnit (https://xunit.github.io/) oraz Moq (https://github.com/Moq/moq4/) do atrap. Do testowej aplikacji dodałem nowy projekt (PostalAndHangfire.Tests), w którym będą znajdowały się wszystkie testy. W normalnej aplikacji można utworzyć oddzielne projekty dla testów jednostkowych oraz integracyjnych.

Testy jednostkowe będą testowały poprawne wywołanie metody Send z IEmailService i w praktyce testować będziemy logikę zamiany danych przychodzących w obiektach domenowych na klasy email. Nie będziemy testowali samej wysyłki wiadomości oraz poprawności wygenerowania treści email – zrobimy to w testach integracyjnych.

Tworząc testy bardzo często wykorzystuje klasy bazowe do wrzucania wspólnej logiki dla testów. Czy to dla testów grup klas (tak jak w tym przypadku, gdzie klasa bazowa będzie dla wszystkich testów klas „*Mailer”), czy dla testów, gdzie pojedyncza klasa testów zawiera testy jednej metody. W przykładzie klasa BaseUnitTests zawiera konfigurację atrap, która jest taka sama dla wszystkich klas „*Mailer”:

Jak widać, tworzę dwie atrapy, gdzie następnie konfiguruje atrapę IEmailServiceFactory, aby metoda Create zwracała atrapę IEmailService.

Sam test jednostkowy wygląda tak:

Tak jak wspomniałem w teście jednostkowym sprawdzamy, czy metoda Send z IEmailService została wywołana z odpowiednimi parametrami. W testach wykorzystuje metody Create, które tworzą testowany obiekt z atrapami zależności, aby nie powielać tego kodu w każdym teście.

Testy integracyjne

Testy jednostkowe były dość proste i testowały tylko fragment kodu wysyłki wiadomości email. Testy integracyjne są bardziej rozbudowana i umożliwią przetestowanie całej logiki, w tym i generowanie treści wiadomości.

Do implementacji testów potrzebny będzie nam serwer SMTP, abyśmy mogli sprawdzić, czy wiadomość została wysłana poprawnie. Do tego celu wykorzystuje bibliotekę netDumbster (https://github.com/cmendible/netDumbster), która właśnie tworzy taki serwer w locie i umożliwia łatwe wyciągnięcie informacji o wysłanych wiadomościach.

Po zainstalowaniu biblioteki z nugeta musimy jeszcze w pliku app.config z projektu z testami dodać informacje o serwerze SMTP, który będzie wykorzystywany podczas testów. Robi się to bardzo podobnie jak w przypadku prawdziwego serwera:

W przypadku netDumbster w app.config podajemy dwie rzeczy: host (localhost) oraz port (np. 25).

W testach jednostkowych również wykorzystuje klasę bazową (BaseIntegrationTests), w której teraz znajdują się pomocnicze metody tworzące tym razem realne zależności dla testowanych klas „*Mailer”:

Sam test integracyjny nie jest dużo bardziej rozbudowany od testu jednostkowego. Dochodzi przede wszystkim uruchomienie testowego serwer SMTP (podajemy w parametrze port z app.config) z biblioteki netDumbster oraz późniejsza seria asertów, które sprawdzają poprawność wysłanej wiadomości email wyciągniętej z testowego serwer SMTP:

W przypadku testowania treści wiadomości osobiście skupiam się na występowaniu poszczególnych fraz (dlatego w przykładzie są dwa aserty). Wiadomości email bardzo często są zbudowane z HTMLa, który z czasem może się zmieniać w zależności od tego jak chcemy, aby wiadomość wyglądała, sam tekst rzadziej się zmienia. Zauważyłem, że utrzymanie później takiego testu jest bardziej czasochłonne, gdyśmy robili asserta dla całej zawartości wiadomości (wraz z kodem HTML). Ale decyzja należy do Ciebie, który sposób chcesz wykorzystać.

Podsumowanie

Testy automatyczne w naszych aplikacjach są bardzo ważnym elementem utrzymania wysokiej jakości oprogramowania. Dlatego warto testować wszystko  w sposób automatyczny. Mam nadzieje, że już wiesz w jaki sposób przetestować wysyłkę wiadomości w aplikacji ASP.NET MVC z wykorzystaniem testów jednostkowych oraz integracyjnych (dzięki bibliotece net Dumbster).

Zaktualizowany kod przykładu znajdziesz na github (https://github.com/danielplawgo/PostalAndHangfire) – zachęcam do pobrania i sprawdzenia w praktyce jak to wszystko działa.

A Ty testujesz automatycznie wysłanie wiadomości email? Czego używasz?

1 thought on “Testowanie wysyłki email w ASP.NET MVC

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *