2015-09-29 4 views
13

Ich habe eine Klasse, die auf den HttpClient von Windows.Web.Http (Windows 10 UAP App) abhängt. Ich möchte einen Komponententest durchführen und muss daher den HttpClient "überspielen", um festzulegen, was ein Get-Call zurückgeben soll. Ich begann mit einem "einfachen" Komponententest mit einem HttpClient unter Verwendung eines handgeschriebenen-verspotteten IHttpFilter und IHttpContent. Es funktioniert nicht wie erwartet und ich bekomme eine InvalidCastException im Test-Explorer.Unit Test Windows.Web.Http HttpClient mit mocked IHttpFilter und IHttpContent, MockedHttpFilter wirft System.InvalidCastException

Der Unit-Test wie folgt aussieht:

[TestMethod] 
    public async Task TestMockedHttpFilter() 
    { 
     MockedHttpContent mockedContent = new MockedHttpContent("Content from MockedHttpContent"); 
     MockedHttpFilter mockedHttpFilter = new MockedHttpFilter(HttpStatusCode.Ok, mockedContent); 

     HttpClient httpClient = new HttpClient(mockedHttpFilter); 
     var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false); 
     // Test stops here, throwing System.InvalidCastException: Specified cast is not valid 

     // Code not reached... 
     var result = await resultContentTask.Content.ReadAsStringAsync(); 
     Assert.AreEqual("Content from MockedHttpContent", result); 
    } 

I IHttpFilter in MockedHttpFilter umgesetzt:

public class MockedHttpFilter : IHttpFilter 
{ 
    private HttpStatusCode _statusCode; 
    private IHttpContent _content; 

    public MockedHttpFilter(HttpStatusCode statusCode, IHttpContent content) 
    { 
     _statusCode = statusCode; 
     _content = content; 
    } 

    public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request) 
    { 
     return AsyncInfo.Run<HttpResponseMessage, HttpProgress>((token, progress) => 
     Task.Run<HttpResponseMessage>(()=> 
     { 
      HttpResponseMessage response = new HttpResponseMessage(_statusCode); 
      response.Content = _content; 
      return response; // Exception thrown after return, but not catched by code/debugger... 
     })); 
    } 
} 

I IHttpContent in MockedHttpContent umgesetzt:

public class MockedHttpContent : IHttpContent 
{ 
    private string _contentToReturn; 

    public MockedHttpContent(string contentToReturn) 
    { 
     _contentToReturn = contentToReturn; 
    } 

    public HttpContentHeaderCollection Headers 
    { 
     get 
     { 
      return new HttpContentHeaderCollection(); 
     } 
    } 

    public IAsyncOperationWithProgress<string, ulong> ReadAsStringAsync() 
    { 
     return AsyncInfo.Run<string, ulong>((token, progress) => Task.Run<string>(() => 
     { 
      return _contentToReturn; 
     })); 
    } 
} 

Der Fehler in dem Test Explorer Ergebnisansicht:

Test Name: TestMockedHttpFilter 
Test FullName: xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter 
Test Source: xxx.UnitTests\xxxHttpClientUnitTests.cs : line 22 
Test Outcome: Failed 
Test Duration: 0:00:00.1990313 

Result StackTrace: 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult() 
    at xxx.UnitTests.xxxHttpClientUnitTests.<TestMockedHttpFilter>d__1.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
Result Message: Test method xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter threw exception: 
System.InvalidCastException: Specified cast is not valid. 

Erstens, nicht sicher, warum die Ausnahme ausgelöst wird/was ich falsch mache. Vielleicht kann mir jemand in die richtige Richtung zeigen oder einen Hinweis geben, was ich als nächstes prüfen/testen soll?

Zweitens gibt es eine bessere Möglichkeit, Testcode mit einer HttpClient Abhängigkeit (Windows 10 UAP) zu verbinden?

+0

Haben Sie Ihr Problem gelöst? Das interessiert mich auch. – Corcus

+0

Leider nicht. Wegen hoher Arbeitsbelastung, Stress zu Hause und an der Universität kann ich momentan nicht an meinem privaten Projekt arbeiten. – 1ppCH

+0

Dies ist nicht wirklich der Weg zum Komponententest, sollten Sie externe Abhängigkeiten und dann nur Unit-Test-Code, den Sie die volle Kontrolle über haben. Das Einbringen von Mocks in ein Framework, das Sie nicht kontrollieren können, scheint falsch zu sein, mit Ausnahme von spezifischen Komponententests. Ein anderes Problem ist, dass der Rückgabetyp für [TestMethod] nicht ungültig sein sollte? –

Antwort

0

Es scheint, Sie sind nicht eine AsTask Überlastung verwendet, die den Rückgabewert von httpClient.SendRequestAsync(...)

versuchen Änderung dieser

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false); 

dieser

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask<IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress>>().ConfigureAwait(false); 
+0

Vielen Dank für Ihre Antwort. Ich habe versucht, mit Ihnen vorgeschlagene Änderung, aber das gibt mir den Compiler-Fehler: Fehler \t CS1929 \t 'IAsyncOperationWithProgress ' enthält keine Definition für 'AsTask' und die beste Erweiterung Methode Überladung 'WindowsRuntimeSystemExtensions.AsTask > (IAsyncActionWithProgress >) 'benötigt einen Empfänger vom Typ' IAsyncActionWithProgress > '' – 1ppCH

0

einfachste Lösung ist, passend wäre mit Diese Bibliothek zum Spotten HttpClient

https://github.com/richardszalay/mockhttp

PM> Install-Package RichardSzalay.MockHttp 

Getestet habe ich es auch mit dem beigefügten Code

 //Arrange 
     var mockHttp = new MockHttpMessageHandler(); 
     mockHttp.When("http://dontcare.ch") 
      .Respond("application/txt", "Content from MockedHttpContent"); // Respond with content 
     var client = mockHttp.ToHttpClient(); 

     //Act 
     var response = await client.GetAsync("http://dontcare.ch"); 
     var result = await response.Content.ReadAsStringAsync(); 

     //Assert 
     Assert.AreEqual("Content from MockedHttpContent", result); 
1

Sie Unit-Test keinen Sinn macht, Sie sind nicht wirklich Ihre Klasse zu testen, sondern versuchen Sie Testen Sie die HttpClient-Klasse. Ich nehme einige Dinge an, da wir die zu testende Methode in Ihrer Frage nicht haben; Ich gehe davon aus, dass Sie so etwas wie die folgenden tun:

public async Task<MyCustomClass> MethodUnderTest() 
{ 
    // ... 

    using (var client = new HttpClient(...)) 
    { 
     // ... 

     var json = response.Content.ReadAsStringAsync(); 
     return JsonConvert.Deserialize<MyCustomClass>(json); 
    } 
} 

Wenn dies der Fall ist, dann müssen Sie verstehen, dass Ihre Klasse nicht bestimmte Abhängigkeiten akzeptiert. Sie können die Klasse entweder so ändern, dass jede externe Abhängigkeit injiziert wird, aber das könnte ein leichter Overkill sein ... wollen Sie wirklich, dass jeder, der Ihre Klasse konsumiert und instanziiert, einen HttpClient für Sie bereitstellen muss? Also, die bessere Alternative ist, ein spöttisches Framework zu verwenden, das eine Abhängigkeit einer konkreten Klasse verspotten kann; die meisten spöttischen Frameworks können mit spottenden Interfaces umgehen (die sind einfach), aber nur sehr wenige können spöttische konkrete Klassen behandeln.

Das Framework, das ich empfehlen würde, ist das Microsoft Fakes Framework. Microsoft Fakes unterstützt Shims (Isolation) und Stubs (Mocks).Isolation ist das, was Sie brauchen, um Mitgliederaufrufe von konkreten Klassen zu kontrollieren.

Also, bei meinem Beispiel, welche Mitglieder müssen gesteuert werden?

  1. HttpResponseMessage.Content_Get
  2. HttpContent.ReadAsStringAsync

Ich glaube nicht, dass Sie das Verhalten des JsonConvert.Deserialize<T>() Mitglied ändern müssen, aber man könnte, wenn man wollte. Das Framework von Microsoft Fakes ist zunächst etwas entmutigend, aber sobald Sie es verwenden und es zum Laufen bringen, wird es einfacher zu verwenden. Alternativ können Sie auch andere Frameworks verwenden, die Isolation zu unterstützen:

  • JustMock von Telerik
  • TypeMock Trenner

Vielleicht andere existieren, bin ich nicht mit ihnen vertraut.