2012-03-20 11 views
27

Ho visto questa domanda comparire in alcuni punti e non ho visto grandi risposte. Dato che ho dovuto farlo da solo alcune volte, ho pensato di pubblicare la mia soluzione. Se hai qualcosa di meglio, per favore posta.Come posso testare un DelegatingHandler personalizzato nell'API Web ASP.NET MVC 4?

N.B. Si sta utilizzando la versione Beta 2 di Web API di ASP.NET MVC 4 - le versioni future potrebbero cambiare!

Update: Questo funziona ancora in ASP.NET MVC 4 RC

risposta

42

In questo approccio, creo un TestHandler e impostarlo come la InnerHandler di proprietà del gestore in prova.

L'handler in prova può essere passato a un HttpClient - questo può sembrare non intuitivo se si sta scrivendo un gestore lato server, ma questo è in realtà un modo leggero per testare un gestore - verrà chiamato in allo stesso modo in un server.

TestHandler restituirà solo un HTTP 200 per impostazione predefinita, ma il costruttore accetta una funzione che è possibile utilizzare per fare asserzioni sul messaggio di richiesta passato dal gestore in prova. Finalmente puoi fare affermazioni sul risultato della chiamata SendAsync dal client.

Una volta che tutto è configurato, chiamare SendAsync sull'istanza client per richiamare il gestore. La richiesta verrà passata al gestore, che passerà a TestHandler (supponendo che passi la chiamata), che restituirà una risposta al gestore.

gestore

Il test è simile al seguente:

public class TestHandler : DelegatingHandler 
{ 
    private readonly Func<HttpRequestMessage, 
     CancellationToken, Task<HttpResponseMessage>> _handlerFunc; 

    public TestHandler() 
    { 
     _handlerFunc = (r, c) => Return200(); 
    } 

    public TestHandler(Func<HttpRequestMessage, 
     CancellationToken, Task<HttpResponseMessage>> handlerFunc) 
    { 
     _handlerFunc = handlerFunc; 
    } 

    protected override Task<HttpResponseMessage> SendAsync(
     HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     return _handlerFunc(request, cancellationToken);    
    } 

    public static Task<HttpResponseMessage> Return200() 
    { 
     return Task.Factory.StartNew(
      () => new HttpResponseMessage(HttpStatusCode.OK)); 
    } 
} 

Esempio di utilizzo con un immaginario MyHandler in prova. Utilizza NUnit per il asserisce .:

var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); 
httpRequestMessage.Headers.Add("username", "test"); 

var handler = new MyHandler() 
{ 
    InnerHandler = new TestHandler((r,c) => 
    { 
     Assert.That(r.Headers.Contains("username")); 
     return TestHandler.Return200(); 
    }) 
}; 

var client = new HttpClient(handler); 
var result = client.SendAsync(httpRequestMessage).Result; 

Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 

Il comportamento predefinito di TestHandler è probabilmente bene per molti test e rende il codice più semplice. La configurazione del gestore in prova si presenta quindi come questo:

var handler = new MyHandler(); 
handler.InnerHandler = new TestHandler(); 

Mi piace questo approccio perché mantiene tutte le affermazioni nel metodo di prova, e il TestHandler è molto riutilizzabile.

+0

come si gestisce l'oggetto richiesta qui. request.createresponse non riesce poiché non vi è alcuna httpconfiguration.quindi se ne aggiungi uno puoi farlo, ma cosa succede se sei interessato ad altre proprietà di richiesta associate al messaggio di richiesta http che hai creato? – Steve

+0

Il mio obiettivo qui è puramente per testare il gestore; quindi non è il tuo suggerimento di aggiungere la HttpConfiguration sufficiente? Qual è il tuo scenario? –

+0

Ho dovuto aggiungere wrapper attorno a request.content, request.properties e request.getroutedata. tutto va bene in test landia – Steve

0

Ho creato quanto segue per testare DelegatingHandlers. È utile per i gestori che utilizzano HttpRequestMessage.DependencyScope per risolvere le dipendenze utilizzando il framework IoC preferito, ad es. un WindsorDependencyResolver con un WindsorContainer:

public class UnitTestHttpMessageInvoker : HttpMessageInvoker 
    { 
     private readonly HttpConfiguration configuration; 

     public UnitTestHttpMessageInvoker(HttpMessageHandler handler, IDependencyResolver resolver) 
     : base(handler, true) 
     { 
      this.configuration = new HttpConfiguration(); 
      configuration.DependencyResolver = resolver; 
     } 

     [DebuggerNonUserCode] 
     public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
     { 
      if (request == null) 
      { 
       throw new ArgumentNullException("request"); 
      } 

      request.Properties["MS_HttpConfiguration"] = this.configuration; 
      return base.SendAsync(request, cancellationToken); 
     } 
    } 
4

stavo solo cercando la stessa cosa, ma si avvicinò con un approccio più sintetico che non ha utilizzato client HTTP. Volevo un test per affermare che il gestore di messaggi consumava un componente di logging deriso. Non avevo davvero bisogno che il gestore interno funzionasse, solo per "spegnerlo" per soddisfare il test dell'unità. Opere per il mio scopo :)

//ARRANGE 
     var logger = new Mock<ILogger>(); 
     var handler= new ServiceLoggingHandler(logger.Object); 
     var request = ControllerContext.CreateHttpRequest(Guid.NewGuid(), "http://test",HttpMethod.Get); 

     handler.InnerHandler = new Mock<HttpMessageHandler>(MockBehavior.Loose).Object; 

     request.Content = new ObjectContent<CompanyRequest>(Company.CreateCompanyDTO(), new JsonMediaTypeFormatter()); 
     var invoker = new HttpMessageInvoker(handler); 

     //ACT 
     var result = invoker.SendAsync(request, new System.Threading.CancellationToken()).Result; 

//ASSERT 
<Your assertion> 
0

Ho anche trovato questa risposta perché ho il mio gestore personalizzato e voglio testarlo Stiamo utilizzando NUnit e Moq, quindi penso che la mia soluzione può essere utile per qualcuno

using Moq; 
    using Moq.Protected; 
    using NUnit.Framework; 
namespace Unit.Tests 
{ 
    [TestFixture] 
    public sealed class Tests1 
    { 
     private HttpClient _client; 
     private HttpRequestMessage _httpRequest; 
     private Mock<DelegatingHandler> _testHandler; 

     private MyCustomHandler _subject;//MyCustomHandler inherits DelegatingHandler 

     [SetUp] 
     public void Setup() 
     { 
      _httpRequest = new HttpRequestMessage(HttpMethod.Get, "/someurl"); 
      _testHandler = new Mock<DelegatingHandler>(); 

      _subject = new MyCustomHandler // create subject 
      { 
       InnerHandler = _testHandler.Object //initialize InnerHandler with our mock 
      }; 

      _client = new HttpClient(_subject) 
      { 
       BaseAddress = new Uri("http://localhost") 
      }; 
     } 

     [Test] 
     public async Task Given_1() 
     { 
      var mockedResult = new HttpResponseMessage(HttpStatusCode.Accepted); 

      void AssertThatRequestCorrect(HttpRequestMessage request, CancellationToken token) 
      { 
       Assert.That(request, Is.SameAs(_httpRequest)); 
       //... Other asserts 
      } 

      // setup protected SendAsync 
      // our MyCustomHandler will call SendAsync internally, and we want to check this call 
      _testHandler 
       .Protected() 
       .Setup<Task<HttpResponseMessage>>("SendAsync", _httpRequest, ItExpr.IsAny<CancellationToken>()) 
       .Callback(
        (Action<HttpRequestMessage, CancellationToken>)AssertThatRequestCorrect) 
       .ReturnsAsync(mockedResult); 

      //Act 
      var actualResponse = await _client.SendAsync(_httpRequest); 

      //check that internal call to SendAsync was only Once and with proper request object 
      _testHandler 
       .Protected() 
       .Verify("SendAsync", Times.Once(), _httpRequest, ItExpr.IsAny<CancellationToken>()); 

      // if our custom handler modifies somehow our response we can check it here 
      Assert.That(actualResponse.IsSuccessStatusCode, Is.True); 
      Assert.That(actualResponse, Is.EqualTo(mockedResult)); 
      //...Other asserts 
     } 
    } 
} 
Problemi correlati