Ich habe einige Probleme beim Versuch, meinen Code für Unit-Tests zu verpacken. Das Problem ist das. Ich habe die Schnittstelle IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
Und die Klasse, die es benutzt, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
Und dann die Connection-Klasse, die simpleIOC verwendet, um die Client-Implementierung einzufügen:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
Und dann habe ich ein Unit-Test-Projekt mit dieser Klasse:
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?
}
Jetzt werde ich natürlich Methoden in der Connection-Klasse haben, die Daten (JSON) von meinem Back-End abrufen. Ich möchte jedoch Komponententests für diese Klasse schreiben, und natürlich möchte ich keine Tests gegen das echte Back-End schreiben, sondern gegen ein verspottetes. Ich habe versucht, eine gute Antwort darauf zu googeln, ohne großen Erfolg. Ich kann und habe Moq benutzt, um mich vorher zu verspotten, aber niemals auf so etwas wie httpClient. Wie soll ich dieses Problem angehen?
Danke im Voraus.
quelle
HttpClient
in Ihrer Benutzeroberfläche anzeigen , liegt das Problem darin. Sie zwingen Ihren Client, dieHttpClient
konkrete Klasse zu verwenden. Stattdessen sollten Sie eine Abstraktion desHttpClient
.Antworten:
Ihre Schnittstelle macht die konkrete
HttpClient
Klasse verfügbar . Daher sind alle Klassen, die diese Schnittstelle verwenden, daran gebunden. Dies bedeutet, dass sie nicht verspottet werden kann.HttpClient
erbt nicht von einer Schnittstelle, so dass Sie Ihre eigene schreiben müssen. Ich schlage ein dekoratorisches Muster vor:Und deine Klasse wird so aussehen:
Der Punkt bei all dem ist, dass
HttpClientHandler
es seine eigenen erstelltHttpClient
. Sie können dann natürlich mehrere Klassen erstellen, dieIHttpHandler
auf unterschiedliche Weise implementiert werden.Das Hauptproblem bei diesem Ansatz ist , dass Sie tatsächlich eine Klasse schreiben , die Methoden in einer anderen Klasse nur Anrufe, jedoch könnten Sie eine Klasse , die erbt von
HttpClient
(Siehe Nkosi des Beispiel , es ist ein viel besserer Ansatz als meins). Das Leben wäre viel einfacher, wenn manHttpClient
eine Schnittstelle hätte, die man verspotten könnte, leider nicht.Dieses Beispiel ist jedoch nicht das goldene Ticket.
IHttpHandler
stützt sich immer noch aufHttpResponseMessage
, was zumSystem.Net.Http
Namespace gehört. Wenn Sie also andere Implementierungen als benötigenHttpClient
, müssen Sie eine Art Mapping durchführen, um deren Antworten inHttpResponseMessage
Objekte zu konvertieren . Dies ist natürlich nur dann ein Problem, wenn Sie mehrere Implementierungen von verwenden müssen,IHttpHandler
aber es sieht nicht so aus, als ob Sie dies tun. Es ist nicht das Ende der Welt, aber es ist etwas, worüber Sie nachdenken müssen.Wie auch immer, Sie können sich einfach lustig machen,
IHttpHandler
ohne sich um die konkreteHttpClient
Klasse kümmern zu müssen, da sie abstrahiert wurde.Ich empfehle, die nicht asynchronen Methoden zu testen , da diese immer noch die asynchronen Methoden aufrufen, ohne sich um das Testen von asynchronen Methoden durch Einheiten kümmern zu müssen (siehe hier)
quelle
Die Erweiterbarkeit von HttpClient liegt in der
HttpMessageHandler
Übergabe an den Konstruktor. Es ist beabsichtigt, plattformspezifische Implementierungen zuzulassen, aber Sie können es auch verspotten. Es ist nicht erforderlich, einen Dekorations-Wrapper für HttpClient zu erstellen.Wenn Sie ein DSL der Verwendung von Moq vorziehen, habe ich eine Bibliothek auf GitHub / Nuget, die die Dinge etwas einfacher macht: https://github.com/richardszalay/mockhttp
quelle
var client = new HttpClient()
mitvar client = ClientFactory()
und Einrichtung ein Feldinternal static Func<HttpClient> ClientFactory = () => new HttpClient();
und auf der Testebene können Sie dieses Feld neu zu schreiben.HttpClientFactory
wie in injizierenFunc<HttpClient>
. Da ich HttpClient als reines Implementierungsdetail und nicht als Abhängigkeit betrachte, verwende ich die Statik wie oben dargestellt. Ich bin völlig in Ordnung mit Tests, die Interna manipulieren. Wenn mir Pure-ism am Herzen liegt, stehe ich auf vollen Servern und teste Live-Code-Pfade. Wenn Sie irgendeine Art von Schein verwenden, akzeptieren Sie eine Annäherung an das Verhalten, nicht an das tatsächliche Verhalten.Ich stimme einigen anderen Antworten zu, dass der beste Ansatz darin besteht, HttpMessageHandler zu verspotten, anstatt HttpClient zu verpacken. Diese Antwort ist insofern einzigartig, als sie weiterhin HttpClient injiziert, sodass es sich um einen Singleton handelt oder mit Abhängigkeitsinjektion verwaltet wird.
"HttpClient soll einmal instanziiert und während der gesamten Lebensdauer einer Anwendung wiederverwendet werden." ( Quelle ).
Das Verspotten von HttpMessageHandler kann etwas schwierig sein, da SendAsync geschützt ist. Hier ist ein vollständiges Beispiel mit xunit und Moq.
quelle
mockMessageHandler.Protected()
war der Mörder. Danke für dieses Beispiel. Es ermöglicht das Schreiben des Tests, ohne die Quelle zu ändern..ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
Dies ist eine häufige Frage, und ich wollte unbedingt HttpClient verspotten, aber ich glaube, ich bin endlich zu der Erkenntnis gekommen, dass Sie HttpClient nicht verspotten sollten. Es scheint logisch, dies zu tun, aber ich denke, wir wurden von Dingen einer Gehirnwäsche unterzogen, die wir in Open-Source-Bibliotheken sehen.
Wir sehen oft "Clients" da draußen, die wir in unserem Code verspotten, damit wir isoliert testen können. Deshalb versuchen wir automatisch, dasselbe Prinzip auf HttpClient anzuwenden. HttpClient macht tatsächlich viel; Sie können sich das als Manager für HttpMessageHandler vorstellen, also wollen Sie sich darüber nicht lustig machen, und deshalb hat es immer noch keine Schnittstelle. Der Teil, an dem Sie für Unit-Tests oder sogar für das Entwerfen Ihrer Services wirklich interessiert sind, ist der HttpMessageHandler, da dies die Antwort zurückgibt, und Sie können sich darüber lustig machen .
Es ist auch erwähnenswert, dass Sie wahrscheinlich anfangen sollten, HttpClient wie eine größere Sache zu behandeln. Zum Beispiel: Halten Sie die Installation neuer HttpClients auf ein Minimum. Verwenden Sie sie wieder, sie sind so konzipiert, dass sie wiederverwendet werden können, und verbrauchen eine Menge weniger Ressourcen, wenn Sie dies tun. Wenn Sie anfangen, es wie ein größeres Geschäft zu behandeln, wird es sich viel falscher anfühlen, wenn Sie es verspotten wollen, und jetzt wird der Nachrichtenhandler das sein, was Sie injizieren, nicht der Client.
Mit anderen Worten, entwerfen Sie Ihre Abhängigkeiten um den Handler herum anstatt um den Client. Noch besser, abstrakte "Dienste", die HttpClient verwenden, mit denen Sie einen Handler injizieren können, und diesen stattdessen als injizierbare Abhängigkeit verwenden. Dann können Sie in Ihren Tests den Handler fälschen, um die Reaktion zum Einrichten Ihrer Tests zu steuern.
Das Einwickeln von HttpClient ist eine verrückte Zeitverschwendung.
Update: Siehe das Beispiel von Joshua Dooms. Genau das empfehle ich.
quelle
Wie auch in den Kommentaren erwähnt müssen Sie abstrakt weg die
HttpClient
es so nicht zu koppeln. Ich habe in der Vergangenheit etwas Ähnliches gemacht. Ich werde versuchen, das, was ich getan habe, an das anzupassen, was Sie versuchen zu tun.Schauen Sie sich zuerst die
HttpClient
Klasse an und entscheiden Sie, welche Funktionalität sie benötigt.Hier ist eine Möglichkeit:
Wie bereits erwähnt, war dies wiederum für bestimmte Zwecke. Ich habe die meisten Abhängigkeiten von allem, was damit zu tun hat, vollständig abstrahiert
HttpClient
und mich auf das konzentriert, was ich zurückgeben wollte. Sie sollten bewerten, wie Sie das abstrahieren möchten,HttpClient
um nur die erforderliche Funktionalität bereitzustellen.Auf diese Weise können Sie jetzt nur das verspotten, was zum Testen benötigt wird.
Ich würde sogar empfehlen, ganz abzuschaffen
IHttpHandler
und dieHttpClient
Abstraktion zu verwendenIHttpClient
. Aber ich wähle einfach nicht aus, da Sie den Hauptteil Ihrer Handler-Schnittstelle durch die Mitglieder des abstrahierten Clients ersetzen können.Eine Implementierung von
IHttpClient
kann dann verwendet werden, um ein reales / konkretesHttpClient
oder ein anderes Objekt für diese Angelegenheit zu verpacken / anzupassen , das verwendet werden kann, um HTTP-Anforderungen zu stellen, da das, was Sie wirklich wollten, ein Dienst war, der diese Funktionalität gemäß denHttpClient
spezifischen Anforderungen bereitstellte . Die Verwendung der Abstraktion ist ein sauberer (meiner Meinung nach) und SOLID-Ansatz und kann Ihren Code wartbarer machen, wenn Sie den zugrunde liegenden Client gegen etwas anderes austauschen müssen, wenn sich das Framework ändert.Hier ist ein Ausschnitt davon, wie eine Implementierung durchgeführt werden könnte.
Wie Sie im obigen Beispiel sehen können, ist ein Großteil des schweren Hebens, das normalerweise mit der Verwendung verbunden
HttpClient
ist, hinter der Abstraktion verborgen.Ihre Verbindungsklasse kann dann mit dem abstrahierten Client injiziert werden
Ihr Test kann dann verspotten, was für Ihr SUT benötigt wird
quelle
HttpClient
und ein Adapter zum Erstellen Ihrer spezifischen Instanz mitHttpClientFactory
. Dadurch wird das Testen der Logik über die HTTP-Anforderung hinaus trivial, was hier das Ziel ist.Aufbauend auf den anderen Antworten schlage ich diesen Code vor, der keine externen Abhängigkeiten aufweist:
quelle
HttpMessageHandler
selbst implementieren müssen, macht dies nahezu unmöglich - und das müssen Sie, weil die Methoden es sindprotected internal
.Ich denke, das Problem ist, dass Sie es nur ein wenig auf den Kopf gestellt haben.
Wenn Sie sich die Klasse oben ansehen, denke ich, dass Sie das wollen. Microsoft empfiehlt, den Client für eine optimale Leistung am Leben zu halten, damit Sie diese Art von Struktur verwenden können. Auch der HttpMessageHandler ist eine abstrakte Klasse und daher verspottbar. Ihre Testmethode würde dann folgendermaßen aussehen:
Auf diese Weise können Sie Ihre Logik testen, während Sie sich über das Verhalten des HttpClient lustig machen.
Tut mir leid, nachdem ich dies geschrieben und selbst ausprobiert habe, wurde mir klar, dass Sie die geschützten Methoden auf dem HttpMessageHandler nicht verspotten können. Anschließend habe ich den folgenden Code hinzugefügt, um die Injektion eines richtigen Mocks zu ermöglichen.
Die damit geschriebenen Tests sehen dann ungefähr so aus:
quelle
Einer meiner Kollegen bemerkte, dass die meisten
HttpClient
Methoden alleSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
unter der Haube aufgerufen werden. Dies ist eine virtuelle Methode ausHttpMessageInvoker
:Der bei weitem einfachste Weg, sich zu verspotten,
HttpClient
bestand darin, einfach diese bestimmte Methode zu verspotten:und Ihr Code kann die meisten (aber nicht alle)
HttpClient
Klassenmethoden aufrufen , einschließlich einer regulärenÜberprüfen Sie hier, um https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs zu bestätigen
quelle
SendAsync(HttpRequestMessage)
direkt aufruft . Wenn Sie Ihren Code so ändern können, dass diese Komfortfunktion nicht verwendet wird,SendAsync
ist es die sauberste Lösung, die ich gefunden habe, HttpClient direkt durch Überschreiben zu verspotten .Eine Alternative wäre, einen Stub-HTTP-Server einzurichten, der vordefinierte Antworten basierend auf einem Muster zurückgibt, das mit der Anforderungs-URL übereinstimmt. Dies bedeutet, dass Sie echte HTTP-Anforderungen testen, die nicht verspottet werden. In der Vergangenheit hätte dies einen erheblichen Entwicklungsaufwand gekostet und wäre viel zu langsam gewesen, um für Unit-Tests in Betracht gezogen zu werden. Die OSS-Bibliothek WireMock.net ist jedoch einfach zu verwenden und schnell genug, um mit vielen Tests ausgeführt zu werden. Das Setup besteht aus einigen Codezeilen:
Weitere Details und Anleitungen zur Verwendung von Wiremock in Tests finden Sie hier.
quelle
Hier ist eine einfache Lösung, die für mich gut funktioniert hat.
Verwenden der Moq-Mocking-Bibliothek.
Quelle: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
quelle
SendAsync
ohnehin verwendet werden, sodass kein zusätzliches Setup erforderlich ist.Viele der Antworten überzeugen mich nicht.
Stellen Sie sich zunächst vor, Sie möchten eine Methode testen, die verwendet wird
HttpClient
. Sie sollten nichtHttpClient
direkt in Ihrer Implementierung instanziieren . Sie sollten einer Fabrik die Verantwortung übertragen, eine InstanzHttpClient
für Sie bereitzustellen. Auf diese Weise können Sie später in dieser Fabrik verspotten und zurückgeben, was immerHttpClient
Sie wollen (z. B. eine VerspottungHttpClient
und nicht die echte).Sie hätten also eine Fabrik wie die folgende:
Und eine Implementierung:
Natürlich müssten Sie diese Implementierung in Ihrem IoC-Container registrieren. Wenn Sie Autofac verwenden, ist dies ungefähr so:
Jetzt hätten Sie eine ordnungsgemäße und überprüfbare Implementierung. Stellen Sie sich vor, Ihre Methode ist ungefähr so:
Nun zum Testteil.
HttpClient
erstreckt sichHttpMessageHandler
, was abstrakt ist. Lassen Sie uns einen "Mock" erstellenHttpMessageHandler
, der einen Delegaten akzeptiert, sodass wir bei Verwendung des Mocks auch jedes Verhalten für jeden Test einrichten können.Und jetzt und mit Hilfe von Moq (und FluentAssertions, einer Bibliothek, die Unit-Tests besser lesbar macht) haben wir alles, was Sie zum Unit-Test unserer verwendeten Methode PostAsync benötigen
HttpClient
Offensichtlich ist dieser Test albern und wir testen wirklich unseren Schein. Aber du kommst auf die Idee. Sie sollten eine aussagekräftige Logik in Abhängigkeit von Ihrer Implementierung testen, z.
Der Zweck dieser Antwort war es, etwas zu testen, das HttpClient verwendet, und dies ist eine schöne, saubere Möglichkeit, dies zu tun.
quelle
Ich bin etwas spät auf der Party, aber ich benutze gerne Wiremocking ( https://github.com/WireMock-Net/WireMock.Net) ), wann immer dies möglich ist, beim Integrationstest eines Dotnet-Core-Mikroservices mit nachgeschalteten REST-Abhängigkeiten.
Durch die Implementierung einer TestHttpClientFactory, die die IHttpClientFactory erweitert, können wir die Methode überschreiben
HttpClient CreateClient (Stringname)
Wenn Sie also die genannten Clients in Ihrer App verwenden, haben Sie die Kontrolle über die Rückgabe eines HttpClient, der mit Ihrem Wiremock verbunden ist.
Das Gute an diesem Ansatz ist, dass Sie in der Anwendung, die Sie testen, nichts ändern und Kursintegrationstests ermöglichen, die eine tatsächliche REST-Anforderung an Ihren Service ausführen und den json (oder was auch immer) verspotten, den die tatsächliche nachgelagerte Anforderung zurückgeben soll. Dies führt zu präzisen Tests und so wenig Verspottung wie möglich in Ihrer Anwendung.
und
quelle
Da Sie
HttpClient
dieSendAsync
Methode verwenden, um alle auszuführenHTTP Requests
, können Sie dieoverride SendAsync
Methode verwenden und verspottenHttpClient
.Für diesen Wrap erstellen Sie
HttpClient
eininterface
, so etwas wie untenVerwenden Sie dann oben
interface
für die Abhängigkeitsinjektion in Ihrem Dienst, Beispiel untenErstellen Sie jetzt im Unit-Test-Projekt eine Hilfsklasse zum Verspotten
SendAsync
. Hier ist es eineFakeHttpResponseHandler
Klasse, dieinheriting
DelegatingHandler
eine Option zum Überschreiben derSendAsync
Methode bietet . Nach dem Überschreiben derSendAsync
Methode Notwendigkeit , eine Antwort für jedes Setup ,HTTP Request
die ruftSendAsync
Methode, denn das ist ein schaffenDictionary
mitkey
soUri
undvalue
so ,HttpResponseMessage
so dass , wann immer es liegtHTTP Request
und wenn dieUri
BegegnungenSendAsync
des konfigurierte zurückkehrenHttpResponseMessage
.Erstellen Sie eine neue Implementierung,
IServiceHelper
indem Sie das Framework verspotten oder wie folgt. DieseFakeServiceHelper
Klasse können wir verwenden, um dieFakeHttpResponseHandler
Klasse zu injizieren , sodass sie immer dann verwendet wird , wenn dieHttpClient
vonclass
ihr erstellte KlasseFakeHttpResponseHandler class
anstelle der eigentlichen Implementierung verwendet wird.Und im Test konfigurieren Sie
FakeHttpResponseHandler class
durch Hinzufügen vonUri
und erwartetHttpResponseMessage
. DasUri
sollte der tatsächliche seinservice
Endpunkt ,Uri
so dass , wenn dasoverridden SendAsync
Verfahren von den tatsächlichen aufgerufen wirdservice
Implementierung wird es , das SpielUri
inDictionary
und reagiert mit dem konfigurierteHttpResponseMessage
. Nach der Konfiguration injizieren Sie dieFakeHttpResponseHandler object
in die gefälschteIServiceHelper
Implementierung. Fügen SieFakeServiceHelper class
dann den tatsächlichen Dienst ein, wodurch der tatsächliche Dienst dieoverride SendAsync
Methode verwendet.GitHub Link: mit Beispielimplementierung
quelle
Sie können die RichardSzalay MockHttp- Bibliothek verwenden, die den HttpMessageHandler verspottet und ein HttpClient-Objekt zurückgeben kann, das während der Tests verwendet werden soll.
GitHub MockHttp
PM> Installationspaket RichardSzalay.MockHttp
Aus der GitHub-Dokumentation
Beispiel von GitHub
quelle
Dies ist eine alte Frage, aber ich verspüre den Drang, die Antworten mit einer Lösung zu erweitern, die ich hier nicht gesehen habe.
Sie können die Microsoft-Assembly (System.Net.Http) fälschen und dann während des Tests ShinsContext verwenden.
Abhängig von Ihrer Implementierung und Ihrem Test würde ich vorschlagen, alle gewünschten Aktionen zu implementieren, bei denen Sie eine Methode auf dem HttpClient aufrufen und den zurückgegebenen Wert fälschen möchten. Wenn Sie ShimHttpClient.AllInstances verwenden, wird Ihre Implementierung in allen Instanzen gefälscht, die während Ihres Tests erstellt wurden. Wenn Sie beispielsweise die GetAsync () -Methode fälschen möchten, gehen Sie wie folgt vor:
quelle
Ich habe etwas sehr Einfaches gemacht, da ich in einer DI-Umgebung war.
und der Schein ist:
quelle
Sie benötigen lediglich eine Testversion der
HttpMessageHandler
Klasse, die Sie anHttpClient
ctor übergeben. Der Hauptpunkt ist, dass Ihre TestklasseHttpMessageHandler
einenHttpRequestHandler
Delegaten hat, den die Anrufer festlegen und einfach so behandeln können,HttpRequest
wie sie es möchten.Sie können eine Instanz dieser Klasse verwenden, um eine konkrete HttpClient-Instanz zu erstellen. Über den HttpRequestHandler-Delegaten haben Sie die volle Kontrolle über ausgehende http-Anforderungen von HttpClient.
quelle
Inspiriert von der Antwort von PointZeroTwo , hier ein Beispiel mit NUnit und FakeItEasy .
SystemUnderTest
In diesem Beispiel ist die Klasse, die Sie testen möchten - kein Beispielinhalt dafür angegeben, aber ich gehe davon aus, dass Sie das bereits haben!quelle
Möglicherweise muss in Ihrem aktuellen Projekt ein Code geändert werden, aber für neue Projekte sollten Sie unbedingt Flurl verwenden.
https://flurl.dev
Es handelt sich um eine HTTP-Clientbibliothek für .NET mit einer fließenden Schnittstelle, die speziell die Testbarkeit von Code ermöglicht, mit dem HTTP-Anforderungen gestellt werden.
Es gibt viele Codebeispiele auf der Website, aber kurz gesagt, Sie verwenden es so in Ihrem Code.
Fügen Sie die Verwendungen hinzu.
Senden Sie eine Get-Anfrage und lesen Sie die Antwort.
In den Unit-Tests fungiert Flurl als Mock, der so konfiguriert werden kann, dass er sich wie gewünscht verhält und auch die getätigten Anrufe überprüft.
quelle
Nach sorgfältiger Suche fand ich den besten Ansatz, um dies zu erreichen.
quelle
Um meine 2 Cent hinzuzufügen. Um bestimmte http-Anforderungsmethoden zu verspotten, entweder Get oder Post. Das hat bei mir funktioniert.
quelle