Eu tenho alguns problemas ao tentar quebrar meu código para ser usado em testes de unidade. Os problemas são estes. Tenho a interface IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
E a classe que o usa, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
E então a classe Connection, que usa simpleIOC para injetar a implementação do cliente:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
E então eu tenho um projeto de teste de unidade que tem esta classe:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
Agora, obviamente, terei métodos na classe Connection que recuperarão dados (JSON) de um back end. No entanto, quero escrever testes de unidade para esta classe e, obviamente, não quero escrever testes contra o backend real, em vez de um simulado. Tentei pesquisar no Google uma boa resposta para isso, sem muito sucesso. Eu posso e já usei o Moq para simular antes, mas nunca em algo como httpClient. Como devo abordar este problema?
Desde já, obrigado.
fonte
HttpClient
em sua interface é onde está o problema. Você está forçando seu cliente a usar aHttpClient
classe concreta. Em vez disso, você deve expor uma abstração doHttpClient
.Respostas:
Sua interface expõe a
HttpClient
classe concreta , portanto, quaisquer classes que usam essa interface estão vinculadas a ela, o que significa que não pode ser simulada.HttpClient
não herda de nenhuma interface, portanto, você terá que escrever a sua própria. Eu sugiro um padrão de decorador :E sua aula será assim:
O ponto em tudo isso é que
HttpClientHandler
cria o seu próprioHttpClient
, então, é claro, você pode criar várias classes que implementam deIHttpHandler
maneiras diferentes.O principal problema com esta abordagem é que você está efetivamente escrevendo uma classe que só chama métodos em outra classe, no entanto, você poderia criar uma classe que herda de
HttpClient
(Veja o exemplo de Nkosi , é uma abordagem muito melhor do que o meu). A vida seria muito mais fácil seHttpClient
tivesse uma interface da qual você pudesse zombar, infelizmente não.No entanto, este exemplo não é o bilhete dourado.
IHttpHandler
ainda depende deHttpResponseMessage
, que pertence aoSystem.Net.Http
namespace, portanto, se você precisar de outras implementações diferentesHttpClient
, terá que realizar algum tipo de mapeamento para converter suas respostas emHttpResponseMessage
objetos. Isso, é claro, só é um problema se você precisar usar várias implementações de,IHttpHandler
mas não parece que você faça, então não é o fim do mundo, mas é algo em que se pensar.De qualquer forma, você pode simplesmente zombar
IHttpHandler
sem ter que se preocupar com aHttpClient
classe concreta conforme ela foi abstraída.Eu recomendo testar os métodos não assíncronos , pois eles ainda chamam os métodos assíncronos, mas sem o incômodo de ter que se preocupar com os métodos assíncronos de teste de unidade, veja aqui
fonte
A extensibilidade do HttpClient reside no
HttpMessageHandler
passado para o construtor. Sua intenção é permitir implementações específicas da plataforma, mas você também pode simular isso. Não há necessidade de criar um wrapper decorador para HttpClient.Se você preferir um DSL em vez de Moq, tenho uma biblioteca no GitHub / Nuget que torna as coisas um pouco mais fáceis: https://github.com/richardszalay/mockhttp
fonte
var client = new HttpClient()
comvar client = ClientFactory()
e configurar um campointernal static Func<HttpClient> ClientFactory = () => new HttpClient();
e, ao nível de teste você pode reescrever neste campo.HttpClientFactory
as inFunc<HttpClient>
. Visto que vejo o HttpClient puramente como um detalhe de implementação e não uma dependência, usarei a estática conforme ilustrado acima. Estou totalmente bem com testes de manipulação de componentes internos. Se eu me preocupo com o pure-ism, colocarei servidores completos e testarei caminhos de código ao vivo. Usar qualquer tipo de simulação significa que você aceita a aproximação do comportamento, não o comportamento real.Concordo com algumas das outras respostas de que a melhor abordagem é simular HttpMessageHandler em vez de envolver HttpClient. Essa resposta é única porque ainda injeta HttpClient, permitindo que seja um singleton ou gerenciado com injeção de dependência.
"HttpClient deve ser instanciado uma vez e reutilizado ao longo da vida de um aplicativo." ( Fonte ).
Zombar de HttpMessageHandler pode ser um pouco complicado porque SendAsync é protegido. Aqui está um exemplo completo, usando xunit e Moq.
fonte
mockMessageHandler.Protected()
era o assassino. Obrigado por este exemplo. Ele permite escrever o teste sem modificar a fonte..ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
Essa é uma pergunta comum, e eu estava fortemente do lado, querendo a capacidade de zombar do HttpClient, mas acho que finalmente percebi que você não deveria zombar do HttpClient. Parece lógico fazer isso, mas acho que passamos por uma lavagem cerebral por coisas que vemos em bibliotecas de código aberto.
Freqüentemente, vemos "Clientes" por aí que simulamos em nosso código para que possamos testar isoladamente, então tentamos aplicar automaticamente o mesmo princípio ao HttpClient. O HttpClient realmente faz muito; você pode pensar nele como um gerenciador para HttpMessageHandler, então você não quer zombar disso, e é por isso que ele ainda não tem uma interface. A parte na qual você está realmente interessado para teste de unidade, ou até mesmo para projetar seus serviços, é o HttpMessageHandler, já que é ele que retorna a resposta, e você pode zombar disso.
Também vale a pena ressaltar que você provavelmente deve começar a tratar o HttpClient como um negócio maior. Por exemplo: reduza ao mínimo a introdução de novos HttpClients. Reutilize-os, eles são projetados para serem reutilizados e usar uma tonelada de recursos a menos se você o fizer. Se você começar a tratá-lo como algo importante, parecerá muito mais errado querer zombar dele e agora o manipulador de mensagens começará a ser o que você está injetando, não o cliente.
Em outras palavras, projete suas dependências em torno do manipulador em vez do cliente. Melhor ainda, "serviços" abstratos que usam HttpClient, que permitem injetar um manipulador e usá-lo como sua dependência injetável. Então, em seus testes, você pode simular o manipulador para controlar a resposta para configurar seus testes.
Empacotar HttpClient é uma perda de tempo insana.
Atualização: Veja o exemplo de Joshua Dooms. É exatamente o que estou recomendando.
fonte
Como também mencionado nos comentários, você precisa abstrair o
HttpClient
para não ser acoplado a ele. Já fiz algo semelhante no passado. Vou tentar adaptar o que fiz com o que você está tentando fazer.Primeiro, olhe para a
HttpClient
classe e decida quais funcionalidades são fornecidas e são necessárias.Aqui está uma possibilidade:
Novamente, como afirmado antes, isso era para fins específicos. Eu abstraia completamente a maioria das dependências para qualquer coisa que lide
HttpClient
e foque no que eu queria que fosse devolvido. Você deve avaliar como deseja abstrair oHttpClient
para fornecer apenas a funcionalidade necessária desejada.Isso agora permitirá que você simule apenas o que precisa ser testado.
Eu até recomendaria acabar com tudo
IHttpHandler
e usar aHttpClient
abstraçãoIHttpClient
. Mas não estou escolhendo, já que você pode substituir o corpo da interface do manipulador pelos membros do cliente abstraído.Uma implementação de
IHttpClient
pode então ser usada para empacotar / adaptar umHttpClient
objeto real / concreto ou qualquer outro objeto para esse assunto, que pode ser usado para fazer solicitações HTTP, pois o que você realmente queria era um serviço que fornecesse essa funcionalidade conformeHttpClient
aplicável especificamente. Usar a abstração é uma abordagem limpa (minha opinião) e SOLID e pode tornar seu código mais fácil de manter se você precisar trocar o cliente subjacente por outra coisa conforme a estrutura muda.Aqui está um trecho de como uma implementação poderia ser feita.
Como você pode ver no exemplo acima, muito do trabalho pesado geralmente associado ao uso
HttpClient
está oculto por trás da abstração.Sua classe de conexão pode então ser injetada com o cliente abstraído
Seu teste pode simular o que é necessário para o seu SUT
fonte
HttpClient
e um adaptador para criar sua instância específica usandoHttpClientFactory
. Fazer isso torna trivial o teste da lógica além da solicitação HTTP, que é o objetivo aqui.Com base nas outras respostas, sugiro este código, que não possui dependências externas:
fonte
HttpMessageHandler
torna isso quase impossível - e você precisa porque os métodos sãoprotected internal
.Acho que o problema é que você só está um pouco de cabeça para baixo.
Se você olhar para a classe acima, acho que é isso que você quer. A Microsoft recomenda manter o cliente ativo para um desempenho ideal, portanto, esse tipo de estrutura permite que você faça isso. Além disso, o HttpMessageHandler é uma classe abstrata e, portanto, pode ser zombada. Seu método de teste ficaria assim:
Isso permite que você teste sua lógica enquanto simula o comportamento do HttpClient.
Desculpe pessoal, depois de escrever isso e tentar sozinho, percebi que você não pode zombar dos métodos protegidos no HttpMessageHandler. Posteriormente, adicionei o seguinte código para permitir a injeção de um mock adequado.
Os testes escritos com isso se parecem com o seguinte:
fonte
Um dos meus colegas notou que a maioria dos
HttpClient
métodos chama porSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
baixo do capô, que é um método virtual fora deHttpMessageInvoker
:Portanto, de longe, a maneira mais fácil de zombar
HttpClient
era simplesmente zombar desse método específico:e seu código pode chamar a maioria (mas não todos) dos
HttpClient
métodos de classe, incluindo umVerifique aqui para confirmar https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
fonte
SendAsync(HttpRequestMessage)
diretamente. Se você puder modificar seu código para não usar essa função de conveniência, simular HttpClient diretamente substituindo-oSendAsync
é, na verdade, a solução mais limpa que encontrei.Uma alternativa seria configurar um servidor HTTP stub que retornasse respostas prontas com base no padrão que corresponde ao url da solicitação, o que significa que você testa solicitações HTTP reais, não simulações. Historicamente, isso exigiria um esforço significativo de desenvolvimento e demoraria muito para ser considerado para testes de unidade. No entanto, a biblioteca OSS WireMock.net é fácil de usar e rápida o suficiente para ser executada com muitos testes, portanto, pode valer a pena considerar. A configuração consiste em algumas linhas de código:
Você pode encontrar mais detalhes e orientações sobre como usar wiremock em testes aqui.
fonte
Aqui está uma solução simples, que funcionou bem para mim.
Usando a biblioteca de simulação do moq.
Fonte: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
fonte
SendAsync
qualquer maneira, então nenhuma configuração extra é necessária.Não estou convencido por muitas das respostas.
Em primeiro lugar, imagine que você deseja fazer um teste de unidade de um método que usa
HttpClient
. Você não deve instanciarHttpClient
diretamente em sua implementação. Você deve injetar uma fábrica com a responsabilidade de fornecer uma instância deHttpClient
para você. Dessa forma, você pode fazer o mock mais tarde naquela fábrica e retornar oHttpClient
que quiser (por exemplo: um mockHttpClient
e não o real).Então, você teria uma fábrica como a seguinte:
E uma implementação:
Claro, você precisaria registrar em seu contêiner IoC esta implementação. Se você usar o Autofac, seria algo como:
Agora você teria uma implementação adequada e testável. Imagine que seu método seja algo como:
Agora, a parte de teste.
HttpClient
estendeHttpMessageHandler
, que é abstrato. Vamos criar um "mock" deHttpMessageHandler
que aceita um delegado para que, ao usarmos o mock, possamos também configurar cada comportamento para cada teste.E agora, e com a ajuda do Moq (e FluentAssertions, uma biblioteca que torna os testes de unidade mais legíveis), temos tudo o que é necessário para testar nosso método PostAsync que usa
HttpClient
Obviamente, esse teste é bobo, e estamos realmente testando nosso mock. Mas você entendeu. Você deve testar uma lógica significativa, dependendo de sua implementação, como ..
O objetivo desta resposta foi testar algo que usa HttpClient e esta é uma maneira limpa e agradável de fazer isso.
fonte
Entrando na festa um pouco tarde, mas gosto de usar wiremocking ( https://github.com/WireMock-Net/WireMock.Net ) sempre que possível no teste de integração de um microsserviço central dotnet com dependências REST downstream.
Ao implementar um TestHttpClientFactory estendendo o IHttpClientFactory, podemos substituir o método
HttpClient CreateClient (nome da string)
Portanto, ao usar os clientes nomeados em seu aplicativo, você tem o controle de devolver um HttpClient conectado à sua wiremock.
O bom dessa abordagem é que você não está mudando nada no aplicativo que está testando e permite que os testes de integração do curso façam uma solicitação REST real para o seu serviço e simulem o json (ou qualquer outro) que a solicitação downstream real deva retornar. Isso leva a testes concisos e o mínimo de simulação possível em seu aplicativo.
e
fonte
Uma vez que
HttpClient
use oSendAsync
método para realizar tudoHTTP Requests
, você pode usar o métodooverride SendAsync
e simular oHttpClient
.Para esse envoltório criando
HttpClient
uminterface
, algo como abaixoEm seguida, use acima
interface
para injeção de dependência em seu serviço, exemplo abaixoAgora, no projeto de teste de unidade, crie uma classe auxiliar para simulação
SendAsync
. Aqui está umaFakeHttpResponseHandler
classe queinheriting
DelegatingHandler
fornecerá uma opção para sobrescrever oSendAsync
método. Após substituir oSendAsync
método é necessário configurar uma resposta para cada umHTTP Request
que está chamando oSendAsync
método, para isso crie umDictionary
comkey
asUri
evalue
asHttpResponseMessage
para que sempre que houver umHTTP Request
e se asUri
correspondênciasSendAsync
retornem o configuradoHttpResponseMessage
.Crie uma nova implementação para a
IServiceHelper
estrutura de simulação ou como a seguir. EstaFakeServiceHelper
classe que podemos usar para injetar aFakeHttpResponseHandler
classe para que sempre que oHttpClient
criado por esteclass
ele vai usarFakeHttpResponseHandler class
em vez da execução efectiva.E em teste configure
FakeHttpResponseHandler class
adicionando oUri
e o esperadoHttpResponseMessage
. AUri
deve ser o actualservice
ponto finalUri
para que quando ooverridden SendAsync
método é chamado de realservice
implementação ele irá corresponder aUri
noDictionary
e responder com o configuradoHttpResponseMessage
. Depois de configurar, injete oFakeHttpResponseHandler object
para aIServiceHelper
implementação falsa . Em seguida, injete oFakeServiceHelper class
no serviço real que fará com que o serviço real use ooverride SendAsync
método.GitHub Link: que está tendo implementação de amostra
fonte
Você pode usar a biblioteca RichardSzalay MockHttp que simula o HttpMessageHandler e pode retornar um objeto HttpClient para ser usado durante os testes.
GitHub MockHttp
PM> Instalar-Pacote RichardSzalay.MockHttp
Da documentação do GitHub
Exemplo do GitHub
fonte
Essa é uma pergunta antiga, mas sinto vontade de estender as respostas com uma solução que não encontrei aqui.
Você pode simular o conjunto da Microsoft (System.Net.Http) e usar o ShinsContext durante o teste.
Depende da sua implementação e teste, sugiro implementar todo o desempenho desejado onde você chama um método no HttpClient e deseja falsificar o valor retornado. Usar ShimHttpClient.AllInstances irá falsificar sua implementação em todas as instâncias criadas durante seu teste. Por exemplo, se você deseja falsificar o método GetAsync (), faça o seguinte:
fonte
Fiz algo muito simples, pois estava em um ambiente de DI.
e a simulação é:
fonte
Tudo que você precisa é de uma versão de teste da
HttpMessageHandler
classe que você passa para oHttpClient
ctor. O ponto principal é que suaHttpMessageHandler
classe de teste terá umHttpRequestHandler
delegado que os chamadores podem definir e simplesmente manipularHttpRequest
da maneira que quiserem.Você pode usar uma instância desta classe para criar uma instância concreta de HttpClient. Por meio do delegado HttpRequestHandler, você tem controle total sobre as solicitações http de saída de HttpClient.
fonte
Inspirado pela resposta do PointZeroTwo , aqui está um exemplo usando NUnit e FakeItEasy .
SystemUnderTest
neste exemplo está a classe que você deseja testar - nenhum conteúdo de amostra fornecido para ela, mas presumo que você já o tenha!fonte
Talvez haja algum código a ser alterado em seu projeto atual, mas para novos projetos, você deve considerar o uso de Flurl.
https://flurl.dev
É uma biblioteca cliente HTTP para .NET com uma interface fluente que habilita especificamente a capacidade de teste para o código que a usa para fazer solicitações HTTP.
Existem muitos exemplos de código no site, mas em poucas palavras, você os usa assim em seu código.
Adicione os usos.
Envie uma solicitação get e leia a resposta.
Nos testes unitários o Flurl atua como um mock que pode ser configurado para se comportar como desejado e também para verificar as chamadas que foram feitas.
fonte
Depois de pesquisar cuidadosamente, descobri a melhor abordagem para fazer isso.
fonte
Para adicionar meus 2 centavos. Para simular métodos de solicitação http específicos, Get ou Post. Isso funcionou para mim.
fonte