Как я могу использовать FakeItEasy с HttpClient в модульном тесте?
Я пытаюсь выяснить, как использовать FakeItEasy с HttpClient, учитывая следующий код:
public Foo(string key, HttpClient httpClient = null)
{ .. }
public void DoGet()
{
....
if (_httpClient == null)
{
_httpClient = new HttpClient();
}
var response = _httpClient.GetAsync("user/1);
}
public void DoPost(foo Foo)
{
if (_httpClient == null)
{
_httpClient = new HttpClient();
}
var formData = new Dictionary<string, string>
{
{"Name", "Joe smith"},
{"Age", "40"}
};
var response = _httpClient.PostAsync("user",
new FormUrlEncodedContent(formData));
}
поэтому я не уверен, как использовать FakeItEasy
, чтобы притворство свойство HttpClient это GetAsync
и PostAsync
методы.
производственный код не пройдет в HttpClient, но модульный тест пройдет в поддельном экземпляре, сделанном FakeItEasy.
например.
[Fact]
public void GivenBlah_DoGet_DoesSomething()
{
// Arrange.
var httpClient A.Fake<HttpClient>(); // <-- need help here.
var foo = new Foo("aa", httpClient);
// Act.
foo.DoGet();
// Assert....
}
обновление:
Я grok, что FiE (и большинство насмешливых пакетов) работает на интерфейсы и виртуальные методы. Поэтому для этого вопроса давайте просто prentend что GetAsync
и PostAsync
методы являются виртуальными ... пожалуйста :)
4 ответов
вот мой (более или менее) общего назначения FakeHttpMessageHandler.
public class FakeHttpMessageHandler : HttpMessageHandler
{
private HttpResponseMessage _response;
public static HttpMessageHandler GetHttpMessageHandler( string content, HttpStatusCode httpStatusCode )
{
var memStream = new MemoryStream();
var sw = new StreamWriter( memStream );
sw.Write( content );
sw.Flush();
memStream.Position = 0;
var httpContent = new StreamContent( memStream );
var response = new HttpResponseMessage()
{
StatusCode = httpStatusCode,
Content = httpContent
};
var messageHandler = new FakeHttpMessageHandler( response );
return messageHandler;
}
public FakeHttpMessageHandler( HttpResponseMessage response )
{
_response = response;
}
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetResult( _response );
return tcs.Task;
}
}
вот пример его использования из одного из моих тестов, который ожидает некоторого JSON в качестве возвращаемого значения.
const string json = "{\"success\": true}";
var messageHandler = FakeHttpMessageHandler.GetHttpMessageHandler(
json, HttpStatusCode.BadRequest );
var httpClient = new HttpClient( messageHandler );
вы бы сейчас впрыснуть httpClient
в ваш тестируемый класс (используя любой механизм впрыска, который вы предпочитаете) и когда GetAsync
называется messageHandler
выплюнет результат, который вы сказали ему.
Я сделал что-то подобное, когда мне нужно было взаимодействовать со службой Gravatar. Я попытался использовать подделки / насмешки, но обнаружил, что это невозможно с HttpClient. Вместо этого я придумал пользовательский класс HttpMessageHandler, который позволяет мне предварительно загрузить ожидаемый ответ, примерно так:
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Tigra.Gravatar.LogFetcher.Specifications
{
/// <summary>
/// Class LoggingHttpMessageHandler.
/// Provides a fake HttpMessageHandler that can be injected into HttpClient.
/// The class requires a ready-made response message to be passed in the constructor,
/// which is simply returned when requested. Additionally, the web request is logged in the
/// RequestMessage property for later examination.
/// </summary>
public class LoggingHttpMessageHandler : DelegatingHandler
{
internal HttpResponseMessage ResponseMessage { get; private set; }
internal HttpRequestMessage RequestMessage { get; private set; }
public LoggingHttpMessageHandler(HttpResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
RequestMessage = request;
return Task.FromResult(ResponseMessage);
}
}
}
тогда моя настройка тестового контекста идет примерно так:
public class with_fake_gravatar_web_service
{
Establish context = () =>
{
MessageHandler = new LoggingHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK));
GravatarClient = new HttpClient(MessageHandler);
Filesystem = A.Fake<FakeFileSystemWrapper>();
Fetcher = new GravatarFetcher(Committers, GravatarClient, Filesystem);
};
protected static LoggingHttpMessageHandler MessageHandler;
protected static HttpClient GravatarClient;
protected static FakeFileSystemWrapper Filesystem;
}
тогда вот пример теста (спецификации), который его использует:
[Subject(typeof(GravatarFetcher), "Web service")]
public class when_fetching_imagaes_from_gravatar_web_service : with_fake_gravatar_web_service
{
Because of = () =>
{
var result = Fetcher.FetchGravatars(@"c:\"); // This makes the web request
Task.WaitAll(result.ToArray());
//"http://www.gravatar.com/avatar/".md5_hex(lc $email)."?d=404&size=".$size;
UriPath = MessageHandler.RequestMessage.RequestUri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
};
It should_make_request_from_gravatar_dot_com =
() => MessageHandler.RequestMessage.RequestUri.Host.ShouldEqual("www.gravatar.com");
It should_make_a_get_request = () => MessageHandler.RequestMessage.Method.ShouldEqual(HttpMethod.Get);
// see https://en.gravatar.com/site/check/tim@tigranetworks.co.uk
It should_request_the_gravatar_hash_for_tim_long =
() => UriPath.ShouldStartWith("avatar/df0478426c0e47cc5e557d5391e5255d");
static string UriPath;
}
вы можете увидеть полный источник на http://stash.teamserver.tigranetworks.co.uk/users/timlong/repos/tigra.gravatar.logfetcher/browse
FakeItEasy, как и большинство издевательских библиотек, не создает прокси для не абстрактных компонентов. В случае HttpClient
, the GetAsync
и PostAsync
методы ни virtual
, ни abstract
, поэтому вы не можете создавать их реализации заглушки напрямую. См.https://github.com/FakeItEasy/FakeItEasy/wiki/What-can-be-faked.
в этом случае вам нужна другая абстракция как зависимость-та, которая HttpClient
может выполнить, но так же могут быть и другие реализации, включая насмешки / заглушки.
Это не отвечает на ваш вопрос напрямую, но я написал библиотеку некоторое время назад, которая предоставляет API для тушения запросов/ответов. Он довольно гибкий и поддерживает упорядоченное/неупорядоченное сопоставление, а также настраиваемую резервную систему для непревзойденных запросов.
Он доступен на GitHub здесь:https://github.com/richardszalay/mockhttp