2017-10-24 4 views
0

Wir haben einige Klassen in unserem C# -Projekt, die Aufrufe an APIs von Drittanbietern ausführen. Wir verwenden HttpClient-Objekte für die Aufrufe. Wir haben unsere Klassen eingerichtet, in denen wir diese Aufrufe ausführen, um einen HttpClient zu akzeptieren, damit wir beim Testen einen benutzerdefinierten/falschen DelegatingHandler mit dem Client verwenden können.HttpClient.SendAsync verwendet DelegatingHandler beim Testen nicht

Wir haben unsere Klassen wie folgt aufgebaut:

public class CallingService : ApiService 
{ 
    private readonly ISomeOtherService _someOtherService; 

    public CallingService (ILogger logger, 
     IConfigurationManager configurationManager, 
     ISomeOtherService someOtherService) : base(logger, configurationManager) 
    { 
     _someOtherService = someOtherService; 
    } 

    public CallingService (ILogger logger, 
     HttpClient client, 
     IConfigurationManager configurationManager, 
     ISomeOtherService someOtherService) : base(logger, configurationManager, client) 
    { 
     _someOtherService = someOtherService; 
    } 

    private async Task<XmlNodeList> TransmitToApi(string xml_string) 
    { 
     ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12; 
     //.. 
     string type = "application/xml"; 
     var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string))); 
     var targetUri = new Uri(ConfigurationManager.GetAppSetting("ApiUrl")); 
     var message = new HttpRequestMessage 
     { 
      RequestUri = targetUri , 
      Method = HttpMethod.Post, 
      Content = content 
     }; 

     message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); 
     message.Content.Headers.Add("Content-Type", type); 
     message.Headers.Add("someHeader", someData); 

     HttpResponseMessage response = null; 
     try 
     { 
      // Define the cancellation token. 
      CancellationTokenSource source = new CancellationTokenSource(); 
      CancellationToken token = source.Token; 
      response = await Client.SendAsync(message, token); 
     } 
     catch (Exception ex) 
     { 
      throw ex; 
     } 
     //... 
     return someData; 
    } 

Die Basis ApiService-Klasse definiert ein generisches Objekt Httpclient, wenn man nicht vorgesehen ist.

Wir verwenden derzeit SendAsync, so dass wir die Nachrichtenheader definieren können. (Wir haben mehr Header als hier aufgelistet sind.)

Der Test der DelegatingHandler wie folgt definiert:

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

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false) 
    { 
     if (!string.IsNullOrWhiteSpace(content)) 
     { 
      if (asXml) 
      { 
       responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml"); 
      } 
      else 
      { 
       responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json"); 
      } 
     } 
     _fakeResponses.Add(uri, responseMessage); 
    } 

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

Und dann:

[Fact] 
public async Task ItWillDoStuffAndCallApi() 
{ 
    using (var mock = AutoMock.GetLoose()) 
    { 
     mock.Mock<IConfigurationManager>() 
      .Setup(cm => cm.GetAppSetting("ApiUrl")) 
      .Returns("http://example.org/test/"); 

     string testReturnData = GetFileContents("IntegrationTests.SampleData.SampleApiResponseXML.txt"); 
     FakeResponseHandler fakeResponseHandler = new FakeResponseHandler(); 
     fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test/"), 
      new HttpResponseMessage(HttpStatusCode.OK), 
      testReturnData, 
      true); 

     //HttpClient httpClient = new HttpClient(fakeResponseHandler); 
     HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler); 
     mock.Provide(httpClient); 

     var ourService = new CallingService(); 
     ourService.TransmitToApi(someXmlString); 
    } 
} 

Wenn wir den Test ausführen, erhalten wir die Nachricht :

Handler gab keine Antwortnachricht zurück.

Und wir scheinen nie in DelegatingHandler.SendAsync Methode zu bekommen.

Wir haben andere Klassen aufrufen APIs mit HttpClient.PostAsync oder GetAsync, und diese rufen Sie die DelegatingHandler.SendAsync Methode und funktionieren wie erwartet.

Wir haben versucht:

HttpClient httpClient = new HttpClient(fakeResponseHandler); 

und
HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);

Wir haben versucht, auch Client.SendAsync mit und ohne Storno Token.

Warum funktioniert das nicht?

Sollten wir dies umschreiben, um PostAsync zu verwenden?

Antwort

0

Ich würde benötigen die Umsetzung von HttpClientFactory.Create zu sehen und was Client.SendAsync tut eigentlich intern aber trotzdem konnte ich den Beispielcode verwenden, die Sie und füllen die Lücken, wo ich konnte die folgenden zur Arbeit zu kommen:

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

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false) 
    { 
     if (!string.IsNullOrWhiteSpace(content)) 
     { 
      if (asXml) 
      { 
       responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml"); 
      } 
      else 
      { 
       responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json"); 
      } 
     } 
     _fakeResponses.Add(uri, responseMessage); 
    } 

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     var emptyContent = string.Empty; 

     if (request.Content.Headers.ContentType.MediaType == "application/xml") 
      emptyContent = "<empty />"; 

     return Task.FromResult(_fakeResponses.ContainsKey(request.RequestUri) ? 
      _fakeResponses[request.RequestUri] : 
      new HttpResponseMessage(HttpStatusCode.NotFound) 
      { 
       RequestMessage = request, 
       Content = new StringContent(emptyContent) 
      }); 
    } 
} 

Um die Dinge sauber zu machen, verwenden Sie Task.FromResult, um eine Aufgabe in SendAsync zurückzugeben, und stellen Sie auch einen leeren Inhalt bereit, um NULL-Referenzausnahmen zu vermeiden.

public class CallingService 
{ 
    private readonly HttpClient _httpClient; 
    private readonly IConfigurationManager _configurationManager; 

    public CallingService(HttpClient httpClient, 
     IConfigurationManager configurationManager) 
    { 
     _httpClient = httpClient; 
     _configurationManager = configurationManager; 
    } 

    public async Task<XmlNodeList> TransmitToApi(string xml_string) 
    { 
     ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12; 
     //.. 
     string type = "application/xml"; 
     var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string))); 
     var targetUri = new Uri(_configurationManager.GetAppSetting("ApiUrl")); 
     var message = new HttpRequestMessage 
     { 
      RequestUri = targetUri, 
      Method = HttpMethod.Post, 
      Content = content 
     }; 

     message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); 
     message.Content.Headers.Add("Content-Type", type); 

     string somedata; 
     try 
     { 
      // Define the cancellation token. 
      CancellationTokenSource source = new CancellationTokenSource(); 
      CancellationToken token = source.Token; 
      var response = await _httpClient.SendAsync(message, token); 

      somedata = await response.Content.ReadAsStringAsync(); 
     } 
     catch (Exception ex) 
     { 
      throw ex; 
     } 
     //... 
     var xmlDoc = new XmlDocument(); 
     xmlDoc.LoadXml(somedata); 
     return xmlDoc.SelectNodes("*"); 
    } 
} 

Und dann der Test bestanden wird die Instanz von HttpClient-CallingService:

[TestMethod] 
    public async Task TestMethod1() 
    { 
     const string content = @"<root><test>1243</test></root>"; 
     const string httpExample = "http://example.org/test/"; 

     var configurationManager = new Mock<IConfigurationManager>(); 

     configurationManager 
      .Setup(cm => cm.GetAppSetting("ApiUrl")) 
      .Returns(httpExample); 

     var fakeResponseHandler = new FakeResponseHandler(); 
     fakeResponseHandler.AddFakeResponse(new Uri(httpExample), 
      new HttpResponseMessage(HttpStatusCode.OK), content, true); 

     using (var httpClient = new HttpClient(fakeResponseHandler)) 
     { 
      var ourService = new CallingService(httpClient, configurationManager.Object); 

      var result = await ourService.TransmitToApi(content); 

      Assert.AreEqual(content, result.Item(0)?.OuterXml); 
     } 
    } 

Das alles funktioniert so, wenn ich raten müsste - das Problem wäre irgendwo in Ihrem HttpClientFacotry.

Hoffe, dass hilft !! Prost, :)

Verwandte Themen