Mam problemy z opakowaniem kodu do użycia w testach jednostkowych. Problem jest taki. Mam interfejs IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
I klasa, która go używa, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
A następnie klasa Connection, która używa simpleIOC do wstrzyknięcia implementacji klienta:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
A potem mam projekt testu jednostkowego, który ma tę klasę:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
Teraz oczywiście będę mieć metody w klasie Connection, które będą pobierać dane (JSON) z mojego zaplecza. Jednak chcę napisać testy jednostkowe dla tej klasy i oczywiście nie chcę pisać testów na prawdziwym zapleczu, a raczej na wyśmiewanym. Próbowałem znaleźć w Google dobrą odpowiedź bez wielkiego sukcesu. Mogłem wcześniej używać Moq do kpiny, ale nigdy na czymś takim jak httpClient. Jak mam podejść do tego problemu?
Z góry dziękuję.
c#
unit-testing
moq
tjugg
źródło
źródło
HttpClient
w interfejsie jest przyczyną problemu. Zmuszasz klienta do używaniaHttpClient
konkretnej klasy. Zamiast tego, należy odsłonić abstrakcję z poniższychHttpClient
.Odpowiedzi:
Twój interfejs ujawnia konkretną
HttpClient
klasę, dlatego wszelkie klasy, które używają tego interfejsu, są z nim powiązane, co oznacza, że nie można z niego wyśmiać.HttpClient
nie dziedziczy z żadnego interfejsu, więc będziesz musiał napisać własny. Proponuję wzór podobny do dekoratora :Twoja klasa będzie wyglądać tak:
W tym wszystkim chodzi o to
HttpClientHandler
tworzy swoje własneHttpClient
, możesz wtedy oczywiście stworzyć wiele klas, które implementują sięIHttpHandler
na różne sposoby.Głównym problemem związanym z tym podejściem jest to, że skutecznie piszesz klasę, która po prostu wywołuje metody z innej klasy, jednak możesz utworzyć klasę dziedziczącą po
HttpClient
(patrz przykład Nkosi , jest to znacznie lepsze podejście niż moje). Życie byłoby o wiele łatwiejsze, gdybyśHttpClient
miał interfejs, z którego można kpić, niestety tak nie jest.Ten przykład nie jest jednak złotym biletem.
IHttpHandler
wciąż opiera się naHttpResponseMessage
, który należy doSystem.Net.Http
przestrzeni nazw, dlatego jeśli potrzebujesz innych implementacji innych niżHttpClient
, będziesz musiał wykonać pewnego rodzaju mapowanie, aby przekonwertować ich odpowiedzi naHttpResponseMessage
obiekty. To oczywiście tylko problem , jeśli chcesz korzystać z wielu wdrożeń oIHttpHandler
ale to nie wygląda jak to zrobić to nie koniec świata, ale jest to coś do myślenia.W każdym razie możesz po prostu kpić
IHttpHandler
nie martwiąc się o konkretnąHttpClient
klasę, ponieważ została wyabstrahowana.Zalecam testowanie metod innych niż asynchroniczne , ponieważ nadal wywołują one metody asynchroniczne, ale bez kłopotów związanych z testowaniem jednostkowym metod asynchronicznych, zobacz tutaj
źródło
Rozszerzalność HttpClient leży w
HttpMessageHandler
przekazaniu do konstruktora. Jego celem jest umożliwienie implementacji specyficznych dla platformy, ale możesz też z tego kpić. Nie ma potrzeby tworzenia otoki dekoratora dla HttpClient.Jeśli wolisz DSL zamiast Moq, mam bibliotekę na GitHub / Nuget, która trochę ułatwia: https://github.com/richardszalay/mockhttp
źródło
var client = new HttpClient()
zvar client = ClientFactory()
i konfiguracja poluinternal static Func<HttpClient> ClientFactory = () => new HttpClient();
i na poziomie testu można przepisać to pole.HttpClientFactory
jak wFunc<HttpClient>
. Biorąc pod uwagę, że postrzegam HttpClient jako czysto szczegół implementacyjny, a nie zależność, użyję statystyk, tak jak zilustrowałem powyżej. Nie mam nic przeciwko testom manipulującym elementami wewnętrznymi. Jeśli zależy mi na czystym izmie, postawię pełne serwery i przetestuję ścieżki kodu na żywo. Używanie wszelkiego rodzaju szyderstw oznacza, że akceptujesz przybliżone zachowanie, a nie rzeczywiste zachowanie.Zgadzam się z niektórymi innymi odpowiedziami, że najlepszym podejściem jest mockowanie HttpMessageHandler zamiast zawijania HttpClient. Ta odpowiedź jest wyjątkowa, ponieważ nadal wstrzykuje HttpClient, dzięki czemu może być pojedynczą lub zarządzaną za pomocą iniekcji zależności.
( Źródło ).
Mockowanie HttpMessageHandler może być trochę trudne, ponieważ SendAsync jest chroniony. Oto kompletny przykład, używając xunit i Moq.
źródło
mockMessageHandler.Protected()
był zabójca. Dzięki za ten przykład. Pozwala na napisanie testu bez jakiejkolwiek modyfikacji źródła..ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
To jest częste pytanie i byłem mocno po stronie, chcąc kpić z HttpClient, ale myślę, że w końcu doszedłem do wniosku, że nie powinieneś kpić z HttpClient. Wydaje się to logiczne, ale myślę, że zrobiono nam pranie mózgu przez rzeczy, które widzimy w bibliotekach open source.
Często widzimy „klientów”, których mockujemy w naszym kodzie, abyśmy mogli testować w izolacji, więc automatycznie próbujemy zastosować tę samą zasadę do HttpClient. HttpClient faktycznie dużo robi; możesz myśleć o nim jako o menedżerze HttpMessageHandler, więc nie chcesz z tego kpić i dlatego nadal nie ma interfejsu. Część że jesteś naprawdę zainteresowany dla testów jednostkowych lub projektując swoje usługi, nawet, jest HttpMessageHandler ponieważ to właśnie zwraca odpowiedź, a ty możesz kpić, że.
Warto również zaznaczyć, że prawdopodobnie powinieneś zacząć traktować HttpClient jak większą transakcję. Na przykład: ogranicz do minimum tworzenie nowych HttpClients. Używaj ich ponownie, są zaprojektowane tak, aby można je było ponownie wykorzystać i zużywać o wiele mniej zasobów, jeśli to zrobisz. Jeśli zaczniesz traktować to jak większą transakcję, poczujesz się znacznie bardziej źle, chcąc z niej kpić, a teraz program obsługi wiadomości zacznie być tym, co wstrzykujesz, a nie klientem.
Innymi słowy, zaprojektuj zależności wokół programu obsługi zamiast klienta. Jeszcze lepiej, abstrakcyjne „usługi”, które używają HttpClient, które pozwalają na wstrzyknięcie programu obsługi i zamiast tego używają go jako zależności do wstrzykiwania. Następnie w swoich testach możesz sfałszować program obsługi, aby kontrolować reakcję na konfigurację testów.
Pakowanie HttpClient to szalona strata czasu.
Aktualizacja: zobacz przykład Joshua Dooms. To jest dokładnie to, co polecam.
źródło
Jak wspomniano również w komentarzach, musisz usunąć plik
HttpClient
aby nie być z nim powiązanym. W przeszłości zrobiłem coś podobnego. Spróbuję dostosować to, co zrobiłem do tego, co ty próbujesz zrobić.Najpierw spójrz na
HttpClient
klasie i zdecyduj, jaką funkcjonalność zapewnia, która będzie potrzebna.Oto możliwość:
Ponownie, jak wspomniano wcześniej, dotyczyło to szczególnych celów. Całkowicie odrzuciłem większość zależności od wszystkiego, czym się zajmowałem,
HttpClient
i skupiłem się na tym, co chciałem zwrócić. Powinieneś ocenić, jak chcesz wyodrębnićHttpClient
aby zapewnić tylko niezbędną funkcjonalność, której potrzebujesz.Umożliwi to teraz wyśmiewanie tylko tego, co jest potrzebne do przetestowania.
Poleciłbym nawet
IHttpHandler
całkowite zniesienie i użycieHttpClient
abstrakcjiIHttpClient
. Ale po prostu nie wybieram, ponieważ możesz zastąpić treść interfejsu programu obsługi członkami wyabstrahowanego klienta.Implementację tego
IHttpClient
można następnie użyć do opakowania / dostosowania rzeczywistego / konkretnegoHttpClient
lub dowolnego innego obiektu w tym celu, który może być użyty do wysyłania żądań HTTP, ponieważ to, czego naprawdę chciałeś, to usługa zapewniająca tę funkcjonalność zgodnie zHttpClient
konkretnym przypadku. Korzystanie z abstrakcji jest czystym (moim zdaniem) i SOLIDnym podejściem i może sprawić, że kod będzie łatwiejszy w utrzymaniu, jeśli zajdzie potrzeba zmiany klienta bazowego na coś innego, gdy zmieni się struktura.Oto fragment pokazujący, jak można wykonać implementację.
Jak widać na powyższym przykładzie,
HttpClient
za abstrakcją ukryta jest duża część ciężkiego podnoszenia zwykle związanego z używaniem .Klasę połączenia można następnie wstrzyknąć za pomocą abstrakcyjnego klienta
Twój test może następnie kpić z tego, co jest potrzebne do testu SUT
źródło
HttpClient
i adapter do tworzenia konkretnej instancji przy użyciuHttpClientFactory
. Dzięki temu testowanie logiki wykraczającej poza żądanie HTTP jest trywialne, co jest tutaj celem.Opierając się na innych odpowiedziach, proponuję ten kod, który nie ma żadnych zewnętrznych zależności:
źródło
HttpMessageHandler
samemu, sprawia, że jest to prawie niemożliwe - a musisz to zrobić, ponieważ metody sąprotected internal
.Myślę, że problem polega na tym, że masz to trochę do góry nogami.
Jeśli spojrzysz na powyższą klasę, myślę, że tego właśnie chcesz. Firma Microsoft zaleca utrzymywanie klienta przy życiu w celu uzyskania optymalnej wydajności, więc ten typ struktury pozwala na to. Również HttpMessageHandler jest klasą abstrakcyjną i dlatego można ją kopiować. Twoja metoda testowa wyglądałaby wtedy następująco:
Pozwala to przetestować logikę podczas mockowania zachowania HttpClient.
Przepraszam, po napisaniu tego i wypróbowaniu tego samemu zdałem sobie sprawę, że nie można kpić z metod chronionych w HttpMessageHandler. Następnie dodałem następujący kod, aby umożliwić wstrzyknięcie odpowiedniego makiety.
Testy napisane w ten sposób wyglądają mniej więcej tak:
źródło
Jeden z moich kolegów zauważył, że większość
HttpClient
metod wywołujeSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
pod maską, co jest metodą wirtualną zHttpMessageInvoker
:Zdecydowanie najłatwiejszym sposobem wyśmiewania się
HttpClient
było po prostu kpienie z tej konkretnej metody:a twój kod może wywoływać większość (ale nie wszystkie)
HttpClient
metod klas, w tym zwykłąKliknij tutaj, aby potwierdzić https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
źródło
SendAsync(HttpRequestMessage)
bezpośrednio. Jeśli możesz zmodyfikować swój kod, aby nie korzystał z tej wygodnej funkcji, to bezpośrednie mockowanie HttpClient przez zastąpienieSendAsync
jest w rzeczywistości najczystszym rozwiązaniem, jakie znalazłem.Jedną z alternatyw byłoby skonfigurowanie pośredniczącego serwera HTTP, który zwraca gotowe odpowiedzi na podstawie wzorca pasującego do adresu URL żądania, co oznacza, że testujesz rzeczywiste żądania HTTP, a nie fałszywe. W przeszłości wymagałoby to znacznego wysiłku i byłoby zbyt wolne, aby wziąć pod uwagę testy jednostkowe, jednak biblioteka WireMock.net OSS jest łatwa w użyciu i wystarczająco szybka, aby można ją było uruchomić z wieloma testami, więc warto ją rozważyć. Konfiguracja to kilka wierszy kodu:
Więcej szczegółów i wskazówek dotyczących używania wiremock w testach można znaleźć tutaj.
źródło
Oto proste rozwiązanie, które dobrze mi się sprawdziło.
Korzystanie z biblioteki symulującej moq.
Źródło: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
źródło
SendAsync
więc nie jest wymagana dodatkowa konfiguracja.Wiele odpowiedzi mnie nie przekonuje.
Przede wszystkim wyobraź sobie, że chcesz przeprowadzić test jednostkowy metody, która używa
HttpClient
. Nie należy tworzyć instancjiHttpClient
bezpośrednio w implementacji. Powinieneś wstrzyknąć fabrykę odpowiedzialną za dostarczenieHttpClient
dla ciebie instancji . W ten sposób możesz później kpić z tej fabryki i zwrócić cokolwiekHttpClient
zechcesz (np: próbną,HttpClient
a nie prawdziwą).Więc miałbyś taką fabrykę jak poniżej:
I realizacja:
Oczywiście musisz zarejestrować tę implementację w swoim IoC Container. Jeśli używasz Autofac, byłoby to coś takiego:
Teraz miałbyś właściwą i możliwą do przetestowania implementację. Wyobraź sobie, że Twoja metoda to coś takiego:
Teraz część testowa.
HttpClient
rozszerzaHttpMessageHandler
, co jest abstrakcyjne. Stwórzmy "mock" tego,HttpMessageHandler
który akceptuje delegata, więc kiedy używamy mocka, możemy również ustawić każde zachowanie dla każdego testu.A teraz, z pomocą Moq (i FluentAssertions, biblioteki, która sprawia, że testy jednostkowe są bardziej czytelne), mamy wszystko, co potrzebne do testów jednostkowych naszej metody PostAsync, która używa
HttpClient
Oczywiście ten test jest głupi i naprawdę testujemy naszą próbę. Ale masz pomysł. W zależności od implementacji należy przetestować sensowną logikę, na przykład ...
Celem tej odpowiedzi było przetestowanie czegoś, co używa HttpClient i jest to ładny, czysty sposób na zrobienie tego.
źródło
Dołączam do imprezy trochę późno, ale lubię używać wiremocking ( https://github.com/WireMock-Net/WireMock.Net ), gdy tylko jest to możliwe w teście integracji mikrousługi dotnet core z podrzędnymi zależnościami REST.
Implementując TestHttpClientFactory, rozszerzając IHttpClientFactory, możemy przesłonić metodę
HttpClient CreateClient (nazwa ciągu)
Dlatego podczas korzystania z nazwanych klientów w aplikacji masz kontrolę nad zwróceniem HttpClient połączonego z wiremockiem.
Dobrą rzeczą w tym podejściu jest to, że nie zmieniasz niczego w testowanej aplikacji i umożliwia testom integracji kursu wykonanie rzeczywistego żądania REST do Twojej usługi i mockowanie json (lub cokolwiek innego), które powinno zwrócić rzeczywiste żądanie podrzędne. Prowadzi to do zwięzłych testów i jak najmniejszego mockowania w Twojej aplikacji.
i
źródło
Ponieważ
HttpClient
użyjSendAsync
metody, aby wykonać wszystkoHTTP Requests
, możeszoverride SendAsync
metodę i kpić zHttpClient
.Za które owijają tworzenia
HttpClient
Dointerface
, coś jak poniżejNastępnie użyj powyższego
interface
do iniekcji zależności w usłudze, przykład poniżejTeraz w projekcie testów jednostkowych utwórz klasę pomocniczą do mockowania
SendAsync
. Tutaj jest toFakeHttpResponseHandler
klasa, którainheriting
DelegatingHandler
zapewni opcję nadpisaniaSendAsync
metody. Po zastąpieniuSendAsync
metody należy ustawić odpowiedź dla każdego,HTTP Request
który wywołujeSendAsync
metodę, w tym celu utwórzDictionary
zkey
asUri
ivalue
asHttpResponseMessage
tak, aby zawsze, gdy istniejeHTTP Request
i jeśliUri
dopasowanieSendAsync
zwróci skonfigurowaneHttpResponseMessage
.Utwórz nową implementację
IServiceHelper
przez mockowanie frameworka lub jak poniżej. TejFakeServiceHelper
klasy możemy użyć do wstrzyknięciaFakeHttpResponseHandler
klasy, aby za każdym razem, gdyHttpClient
utworzona przez toclass
, używałaFakeHttpResponseHandler class
zamiast rzeczywistej implementacji.W konfiguracji testowej
FakeHttpResponseHandler class
, dodającUri
i oczekiwaneHttpResponseMessage
.Uri
Powinien być rzeczywistyservice
końcowyUri
tak, że gdyoverridden SendAsync
metoda nazywa się od rzeczywistejservice
realizacji będzie odpowiadaćUri
wDictionary
odpowiedzi ze skonfigurowanymHttpResponseMessage
. Po skonfigurowaniu wstrzyknijFakeHttpResponseHandler object
do fałszywejIServiceHelper
implementacji. Następnie wstrzyknij theFakeServiceHelper class
do rzeczywistej usługi, która spowoduje, że rzeczywista usługa będzie używaćoverride SendAsync
metody.GitHub Link: który ma przykładową implementację
źródło
Możesz użyć biblioteki RichardSzalay MockHttp, która mockuje HttpMessageHandler i może zwrócić obiekt HttpClient do użycia podczas testów.
GitHub MockHttp
PM> Zainstaluj pakiet RichardSzalay.MockHttp
Z dokumentacji GitHub
Przykład z GitHub
źródło
To stare pytanie, ale czuję potrzebę poszerzenia odpowiedzi o rozwiązanie, którego tutaj nie widziałem.
Możesz sfałszować asembler Microsoft (System.Net.Http), a następnie użyć ShinsContext podczas testu.
W zależności od implementacji i testu sugerowałbym zaimplementowanie wszystkich pożądanych działań, w których wywołujesz metodę na HttpClient i chcesz sfałszować zwróconą wartość. Użycie ShimHttpClient.AllInstances sfałszuje Twoją implementację we wszystkich instancjach utworzonych podczas testu. Na przykład, jeśli chcesz sfałszować metodę GetAsync (), wykonaj następujące czynności:
źródło
Zrobiłem coś bardzo prostego, ponieważ byłem w środowisku DI.
a mock to:
źródło
Potrzebujesz tylko testowej wersji
HttpMessageHandler
klasy, którą przekazujeszHttpClient
ctorowi. Najważniejsze jest to, że twojaHttpMessageHandler
klasa testowa będzie miałaHttpRequestHandler
delegata, którego wywołujący mogą ustawić i po prostu obsługiwaćHttpRequest
tak, jak chcą.Możesz użyć wystąpienia tej klasy, aby utworzyć konkretne wystąpienie HttpClient. Poprzez delegata HttpRequestHandler masz pełną kontrolę nad wychodzącymi żądaniami http z HttpClient.
źródło
Zainspirowany odpowiedzią PointZeroTwo , oto próbka wykorzystująca NUnit i FakeItEasy .
SystemUnderTest
w tym przykładzie jest to klasa, którą chcesz przetestować - nie podano przykładowej treści, ale zakładam, że już ją masz!źródło
Być może byłby jakiś kod do zmiany w twoim obecnym projekcie, ale w przypadku nowych projektów zdecydowanie powinieneś rozważyć użycie Flurl.
https://flurl.dev
Jest to biblioteka klienta HTTP dla .NET z płynnym interfejsem, który w szczególności umożliwia testowanie kodu używającego go do wykonywania żądań HTTP.
Na stronie jest mnóstwo przykładów kodu, ale w skrócie używasz go w swoim kodzie w ten sposób.
Dodaj zastosowania.
Wyślij żądanie odbioru i przeczytaj odpowiedź.
W testach jednostkowych Flurl działa jako makieta, którą można skonfigurować tak, aby zachowywała się zgodnie z oczekiwaniami, a także weryfikowała wykonane wywołania.
źródło
Po dokładnych poszukiwaniach znalazłem najlepsze podejście, aby to osiągnąć.
źródło
Aby dodać moje 2 centy. Aby udawać określone metody żądania http, Get lub Post. To zadziałało dla mnie.
źródło