2014-03-06 10 views
51

Ich habe einen Dienst, der Microsoft.Net.Http verwendet, um einige Json Daten abzurufen. Groß!Wie kann man in einem .NET Test einen verspotteten HttpClient übergeben?

Natürlich möchte ich nicht, dass mein Komponententest den tatsächlichen Server trifft (sonst ist das ein Integrationstest).

Hier ist mein Service Ctor (die Dependency Injection verwendet ...)

public Foo(string name, HttpClient httpClient = null) 
{ 
... 
} 

Ich bin nicht sicher, wie ich das mit verspotten kann ... sagen .. Moq oder FakeItEasy.

Ich möchte sicherstellen, dass, wenn mein Service ruft GetAsync oder PostAsync .. dann kann ich diese Anrufe fälschen.

Irgendwelche Vorschläge, wie ich das tun kann?

Ich bin -hoping- brauche ich nicht zu meinem eigenen Wrapper zu machen .. denn das ist Mist :(Microsoft kann nicht ein Versehen mit diesem gemacht hat, nicht wahr?

(ja, es ist leicht zu mache Wrapper .. ich habe sie schon mal gemacht ... aber das ist der Punkt!)

+1

_ "Ich bin -Hoping- Ich brauche nicht meinen eigenen Wrapper" _ - Wie viele der 'HttpClient' Methoden und Eigenschaften verwenden Sie? Es kann sich in der Regel als nützlich erweisen, eine Schnittstelle für diese zu erstellen, die Sie dann vortäuschen können. – CodeCaster

+0

(Wie ich im OP erwähnt) Einverstanden - es ist einfach und einfach ... aber ich hätte aber, dass ich nicht müsste? Das ist etwas, das Kern sein sollte. Ist das meine einzige Option? –

+1

Soweit ich weiß, könnten Sie diese Anrufe stubben, wenn sie virtuell wären. Da sie nicht sind, würde ich annehmen, dass Sie einen Wrapper schreiben müssen (was der sauberere Weg ist). – ElGauchooo

Antwort

84

Sie können den Kern HttpMessageHandler durch einen falschen ersetzen Ding, das so aussieht ...

public class FakeResponseHandler : DelegatingHandler 
    { 
     private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>(); 

     public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage) 
     { 
       _FakeResponses.Add(uri,responseMessage); 
     } 

     protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) 
     { 
      if (_FakeResponses.ContainsKey(request.RequestUri)) 
      { 
       return _FakeResponses[request.RequestUri]; 
      } 
      else 
      { 
       return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request}; 
      } 

     } 
    } 

und dann können Sie einen Client erstellen, der den falschen Handler verwenden wird.

var fakeResponseHandler = new FakeResponseHandler(); 
fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test"), new HttpResponseMessage(HttpStatusCode.OK)); 

var httpClient = new HttpClient(fakeResponseHandler); 

var response1 = await httpClient.GetAsync("http://example.org/notthere"); 
var response2 = await httpClient.GetAsync("http://example.org/test"); 

Assert.Equal(response1.StatusCode,HttpStatusCode.NotFound); 
Assert.Equal(response2.StatusCode, HttpStatusCode.OK); 
+0

Schöne Lösung für ein interessantes Problem. Buchstäblich macht alles außer senden HTTP über die Leitung. – Spence

+0

Ihr Beispiel wird nicht so kompiliert wie es ist. Sie müssen Task.FromResult verwenden, um Ihre HttpResponseMessage-Instanzen zurückzugeben. – Ananke

+1

@Ananke Das async-Schlüsselwort der SendAsync-Methode macht diese Magie für Sie. –

1

Du kannst einen Blick auf Microsoft Fakes werfen, speziell auf den Shims-Bereich, mit dem du das Verhalten von ändern kannst Ihr HttpClient selbst. Voraussetzung ist, dass Sie VS Premium oder Ultimate verwenden.

+3

Ich habe keinen Zugriff auf diese beiden teuren Editionen. –

2

Ich würde nur eine kleine Änderung an @Darrel Miller ‚s Antwort machen, die Task.FromResult wird mit der Warnung über ein asynchrones Verfahren zu vermeiden, dass ein await Betreiber erwarten.

public class FakeResponseHandler : DelegatingHandler 
{ 
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>(); 

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage) 
    { 
     _FakeResponses.Add(uri, responseMessage); 
    } 

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) 
    { 
     if (_FakeResponses.ContainsKey(request.RequestUri)) 
     { 
      return Task.FromResult(_FakeResponses[request.RequestUri]); 
     } 
     else 
     { 
      return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request }); 
     } 
    } 
} 
+3

Ich wäre vorsichtig, wenn ich diese Technik in jeder Umgebung verwende, die Leistung ist empfindlich, da es einen Threadwechsel ohne Nutzen verursachen könnte. Wenn Sie sich wirklich um die Warnung sorgen, entfernen Sie einfach das async-Schlüsselwort und verwenden Sie Task.FromResult. –

+4

Wenn die Implementierung nicht taskbasiert ist, ist es besser, Task.FromResult zurückzugeben und das async-Schlüsselwort zu entfernen, anstatt Task.Run zu verwenden. Zurückgeben von Task.FromResult (Antwort); – user3285954

17

Ich weiß, dass dies eine alte Frage, aber ich stolperte bei der Suche zu diesem Thema mit ihm und fand eine sehr schöne Lösung zu machen Tests HttpClient einfacher.

Es ist über nuget verfügbar:

https://github.com/richardszalay/mockhttp

PM> Install-Package RichardSzalay.MockHttp 

Hier ist ein kurzer Blick auf die Nutzung:

var mockHttp = new MockHttpMessageHandler(); 

// Setup a respond for the user api (including a wildcard in the URL) 
mockHttp.When("http://localost/api/user/*") 
     .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON 

// Inject the handler or client into your application code 
var client = new HttpClient(mockHttp); 

var response = await client.GetAsync("http://localost/api/user/1234"); 
// or without await: var response = client.GetAsync("http://localost/api/user/1234").Result; 

var json = await response.Content.ReadAsStringAsync(); 

// No network connection required 
Console.Write(json); // {'name' : 'Test McGee'} 

Mehr Infos auf der GitHub-Projektseite. Hoffe, das kann nützlich sein.