web-dev-qa-db-fra.com

Unité testant les requêtes HTTP en c #

J'écris du code qui appelle un service Web, relit la réponse et fait quelque chose avec. Mon code ressemble nominalement à ceci:

string body = CreateHttpBody(regularExpression, strategy);

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);
request.Method = "POST";
request.ContentType = "text/plain; charset=utf-8";

using (Stream requestStream = request.GetRequestStream())
{
    requestStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length);
    requestStream.Flush();
}

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    byte[] data = new byte[response.ContentLength];

    using (Stream stream = response.GetResponseStream())
    {
        int bytesRead = 0;

        while (bytesRead < data.Length)
        {
            bytesRead += stream.Read(data, bytesRead, data.Length - bytesRead);
        }
    }

    return ExtractResponse(Encoding.UTF8.GetString(data));
}

Les seules parties où je fais réellement une manipulation personnalisée sont dans les méthodes ExtractResponse et CreateHttpBody. Cependant, il ne semble pas juste de tester ces méthodes à l'unité, et espérons que le reste du code se réunira correctement. Existe-t-il un moyen d'intercepter la demande HTTP et de lui fournir des données factices à la place?

[~ # ~] modifier [~ # ~] Ces informations sont désormais obsolètes. Il est beaucoup plus facile de construire ce type de code à l'aide des bibliothèques System.Net.Http.HttpClient .

31
Ceilingfish

Si se moquer de HttpWebRequest et HttpWebResponse devient trop lourd, ou si vous avez besoin de tester du code dans un test d'acceptation, où vous appelez votre code de "l'extérieur", la création d'un faux service est probablement la meilleure façon de procéder.

J'ai en fait écrit une bibliothèque open source appelée MockHttpServer pour aider à cela, ce qui rend super simple la simulation de tous les services externes qui communiquent via HTTP.

Voici un exemple d'utilisation avec RestSharp pour appeler un point de terminaison API avec lui, mais HttpWebRequest fonctionnerait tout aussi bien.

using (new MockServer(3333, "/api/customer", (req, rsp, prm) => "Result Body"))
{
    var client = new RestClient("http://localhost:3333/");
    var result = client.Execute(new RestRequest("/api/customer", Method.GET));
}

Il y a un readme assez détaillé sur la page GitHub qui passe par toutes les options disponibles pour l'utiliser, et la bibliothèque elle-même est disponible via NuGet.

7
Jeffrey Harmon

Je commencerais probablement par refactoriser le code afin de le coupler plus faiblement à une requête HTTP réelle. À l'heure actuelle, ce code semble faire beaucoup de choses.

Cela pourrait être fait en introduisant une abstraction:

public interface IDataRetriever
{
    public byte[] RetrieveData(byte[] request);
}

Désormais, la classe que vous essayez de tester unitairement peut être découplée de la demande HTTP réelle à l'aide du modèle de conception Inversion of Control:

public class ClassToTest
{
    private readonly IDataRetriever _dataRetriever;
    public Foo(IDataRetriever dataRetriever)
    {
        _dataRetriever = dataRetriever;
    }

    public string MethodToTest(string regularExpression, string strategy)
    {
        string body = CreateHttpBody(regularExpression, strategy);
        byte[] result = _dataRetriever.RetrieveData(Encoding.UTF8.GetBytes(body));
        return ExtractResponse(Encoding.UTF8.GetString(result));
    }
}

Il n'est plus de la responsabilité de ClassToTest de traiter une requête HTTP réelle. Il est désormais découplé. Tester le MethodToTest devient une tâche triviale.

Et la dernière partie est évidemment d'avoir une implémentation de l'abstraction que nous avons introduite:

public class MyDataRetriever : IDataRetriever
{
    private readonly string _url;
    public MyDataRetriever(string url)
    {
        _url = url;
    }

    public byte[] RetrieveData(byte[] request)
    {
        using (var client = new WebClient())
        {
            client.Headers[HttpRequestHeader.ContentType] = "text/plain; charset=utf-8";
            return client.UploadData(_url, request);
        }
    }
}

Vous pouvez ensuite configurer votre infrastructure DI préférée pour injecter une instance MyDataRetriever dans le constructeur de classe ClassToTest dans votre application réelle.

6
Darin Dimitrov

Si vous êtes heureux de passer à HttpClient (une bibliothèque client http officielle et portable), j'ai écrit une bibliothèque il y a quelque temps qui pourrait aider à s'appeler MockHttp . Il fournit une API fluide qui vous permet de fournir des réponses aux demandes appariées à l'aide d'une gamme d'attributs.

2
Richard Szalay