UWP Windows.Web.HttpClient fake for unit test - c#

I try to do unit-test REST communication logic for UWP client. With reference to the answer for System.Web.HttpClient, I found that Windows.Net.HttpClient also accepts an arguement called IHttpFilter.
So, I try to make custom response with IHttpFilter but I don't know correct way to make a response.
public class TestFilter : IHttpFilter
{
public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
if (request.Method == HttpMethod.Get)
{
// response fake response for GET...
}
}
public void Dispose()
{
// do nothing
}
}
And the target method for unit-test is as below.
public async Task<string> PostResult(HttpClient httpClient, string username)
{
var json = new JsonObject
{
{"Username",
JsonValue.CreateStringValue(string.IsNullOrEmpty(username) ? CommonKey.UnAuthorizedUserPartitionKey : username)
},
};
var content = new HttpStringContent(json.Stringify());
content.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
// I want to make below line testable...
var response = await httpClient.PostAsync(new Uri(Common.ProcessUrl), content);
try
{
response.EnsureSuccessStatusCode();
return null;
}
catch (Exception exception)
{
return exception.Message ?? "EMPTY ERROR MESSAGE";
}
}
Note that It's NOT duplicate question related to System.Web.HttpClient mocking/faking. What I ask is Windows.Web.HttpClient specifically. I failed to implement with it.
Note that, Windows.Web.Http.IHttpClient is internal accessible and HttpClient is sealed. So hard to do Mock or inherit-and-override it.

While I agree with some that there are better ways to test HttpClient calls, I'll answer your question of how to create a "fake" response with an IHttpFilter implementation (System.Runtime.InteropServices.WindowsRuntime is your friend)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
namespace Project.UnitTesting
{
public class FakeResponseFilter : IHttpFilter
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
{
_fakeResponses.Add(uri, responseMessage);
}
public void Dispose()
{
// Nothing to dispose
}
public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
if (_fakeResponses.ContainsKey(request.RequestUri))
{
var fakeResponse = _fakeResponses[request.RequestUri];
return DownloadStringAsync(fakeResponse);
}
// Alternatively, you might want to throw here if a request comes
// in that is not in the _fakeResponses dictionary.
return DownloadStringAsync(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
}
private IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> DownloadStringAsync(HttpResponseMessage message)
{
return AsyncInfo.Run(delegate (CancellationToken cancellationToken, IProgress<HttpProgress> progress)
{
progress.Report(new HttpProgress());
try
{
return Task.FromResult(message);
}
finally
{
progress.Report(new HttpProgress());
}
});
}
}
}

Related

API GET method doesn't store data in the property in Xamarin Forms

Data is being retrieve from the API succesfully, as I can see it here,
response
and then goes to the jsonstring, but never gets to the CantGet variable
I need it to be store in my property so I can use the value.
This is my API return:
[{"CantPremio":"70"}
Then this is my property:
using System;
using System.Collections.Generic;
using System.Text;
namespace ServLottery
{
public class GetCantPremio
{
public long CantPremio { get; set; }
}
}
This is the Get task
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
namespace ServLottery
{
public class RestClient
{
HttpClient client = new HttpClient();
public async Task<T> Get<T>(string URL)
{
try
{
var response = await client.GetAsync(URL);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var jsonstring = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonstring);
}
}
catch
{
}
return default(T);
}
}
}
Finally this is the call:
private async void GetCantDisponible()
{
try
{
RestClient client = new RestClient();
var CantGet = await client.Get<GetCantPremio>("https://servicentroapi.azurewebsites.net/api/GetNumber");
if (CantGet != null)
{
PremiosCantLocal = CantGet.CantPremio.ToString();
}
}
catch (Exception ex)
{
throw ex;
}
}
The api you are accessing is returning an array. So you must deserialize not a simple object but a list.
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<T>>(jsonstring);
Replace the line that deserializes with this one. Should solve the problem
Like kelvin said, set the List<T> for the json array. And then foreach the collection to get the CantPremio.
RestClient:
public class RestClient
{
HttpClient client = new HttpClient();
public async Task<List<T>> Get<T>(string URL)
{
try
{
var response = await client.GetAsync(URL);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var jsonstring = await response.Content.ReadAsStringAsync();
var s = Newtonsoft.Json.JsonConvert.DeserializeObject<List<T>>(jsonstring);
return s;
}
}
catch
{
}
return default(List<T>);
}
}
GetCantDisponible:
private async void GetCantDisponible()
{
try
{
RestClient client = new RestClient();
var CantGet = await client.Get<GetCantPremio>("https://servicentroapi.azurewebsites.net/api/GetNumber");
if (CantGet != null)
{
foreach (var item in CantGet)
{
var PremiosCantLocal = item.CantPremio.ToString();
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Screenshot:
As mentioned, your API is returning an array but you're trying to deserialize it to a single instance. I'd suggest changing the call site of your client to pass a list for the type parameter:
List<GetCantPremio> CantGet = await client.Get<List<GetCantPremio>>("https://servicentroapi.azurewebsites.net/api/GetNumber");
Note that CantGet is now a List. If you are only looking for one object you could just add on a FirstOrDefault():
GetCantPremio CantGet = await client.Get<List<GetCantPremio>>("https://servicentroapi.azurewebsites.net/api/GetNumber")?.FirstOrDefault();

Refactoring code to allow for unit testing of HttpClient

I am dealing with a piece of code that looks like this:
public class Uploader : IUploader
{
public Uploader()
{
// assign member variables to dependency injected interface implementations
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
result = await client.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
I am trying to unit test the Upload function. In particular, I need to mock the HttpClient. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler instead and pass that to HttpClient and have it return whatever I want.
So, I started along that path by first passing in HttpClient in the constructor as a dependency:
public class Uploader : IUploader
{
private readonly HttpClient m_httpClient; // made this a member variable
public Uploader(HttpClient httpClient) // dependency inject this
{
m_httpClient = httpClient;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
and adding: services.AddSingleton<HttpClient>(); to the ConfigureServices method of Startup.cs.
But now I face a slight issue where the original code specifically creates a HttpClientHandler to pass in. How then do I refactor that to take in a mockable handler?
I find the simplest way is to continue using HttpClient, but pass in a mocking HttpClientHandler such as https://github.com/richardszalay/mockhttp
Code sample from the link above:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}");
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
var json = await response.Content.ReadAsStringAsync();
Console.Write(json); // {'name' : 'Test McGee'}
The Dependency Injection framework built into .NET Core ignores internal constructors, so it will call the parameter-less constructor in this scenario.
public sealed class Uploader : IUploader
{
private readonly HttpClient m_httpClient;
public Uploader() : this(new HttpClientHandler())
{
}
internal Uploader(HttpClientHandler handler)
{
m_httpClient = new HttpClient(handler);
}
// regular methods
}
In your unit tests, you can use the constructor accepting the HttpClientHandler:
[Fact]
public async Task ShouldDoSomethingAsync()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myserver.com/upload")
.Respond("application/json", "{'status' : 'Success'}");
var uploader = new Uploader(mockHttp);
var result = await uploader.UploadAsync();
Assert.Equal("Success", result.Status);
}
Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient.
HttpClientFactory might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.
One way would be to abstract your HTTP functionality into a service i.e. HttpService which implements an interface of IHttpService:
IHttpService
public interface IHttpService
{
Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null);
}
HttpService
public class HttpService : IHttpService
{
private static HttpClient _httpClient;
private const string MimeTypeApplicationJson = "application/json";
public HttpService()
{
_httpClient = new HttpClient();
}
private static async Task<HttpResponseMessage> HttpSendAsync(HttpMethod method, Uri url, string payload,
Dictionary<string, string> headers = null)
{
var request = new HttpRequestMessage(method, url);
request.Headers.Add("Accept", MimeTypeApplicationJson);
if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrWhiteSpace(payload))
request.Content = new StringContent(payload, Encoding.UTF8, MimeTypeApplicationJson);
return await _httpClient.SendAsync(request);
}
public async Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null)
{
return await HttpSendAsync(HttpMethod.Post, url, payload, headers);
}
}
Add to your services:
services.AddSingleton<IHttpService, HttpService>();
In your class you would then inject IHttpService as a dependency:
public class Uploader : IUploader
{
private readonly IHttpService _httpService; // made this a member variable
public Uploader(IHttpService httpService) // dependency inject this
{
_httpService = httpService;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
result = await _httpService.PostAsync(new Uri(url), data);
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
You could then use Moq to mock HttpService in your unit test:
[TestClass]
public class UploaderTests
{
private Mock<IHttpService> _mockHttpService = new Mock<IHttpService>();
[TestMethod]
public async Task WhenStatusCodeIsNot200Ok_ThenErrorMessageReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(url, data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
// act
var result = await uploader.Upload(new Uri(url), data);
// assert
Assert.AreEqual("Some Error Message", result);
}
[TestMethod]
public async Task WhenStatusCodeIs200Ok_ThenNullReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(new Uri(url), data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
// act
var result = await uploader.Upload(url, data);
// assert
Assert.AreEqual(null, result);
}
}

Get JSON from url, save to file, and finally CreateActivationService

Using the Template Studio extension for Visual Studio, I generated a project solution base and am now attempting to interject the app load process with a HTTP request before proceeding the render the page view.
App.xaml.cs
using System;
using Braytech_3.Services;
using Windows.ApplicationModel.Activation;
using Windows.Storage;
using Windows.UI.Xaml;
namespace Braytech_3
{
public sealed partial class App : Application
{
private Lazy<ActivationService> _activationService;
private ActivationService ActivationService
{
get { return _activationService.Value; }
}
public App()
{
InitializeComponent();
APIRequest();
// Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy<T> class.
_activationService = new Lazy<ActivationService>(CreateActivationService);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (!args.PrelaunchActivated)
{
await ActivationService.ActivateAsync(args);
}
}
protected override async void OnActivated(IActivatedEventArgs args)
{
await ActivationService.ActivateAsync(args);
}
private async void APIRequest()
{
//Create an HTTP client object
Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
//Add a user-agent header to the GET request.
var headers = httpClient.DefaultRequestHeaders;
Uri requestUri = new Uri("https://json_url");
//Send the GET request asynchronously and retrieve the response as a string.
Windows.Web.Http.HttpResponseMessage httpResponse = new Windows.Web.Http.HttpResponseMessage();
string httpResponseBody = "";
try
{
//Send the GET request
httpResponse = await httpClient.GetAsync(requestUri);
httpResponse.EnsureSuccessStatusCode();
httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
APITempSave(httpResponseBody);
}
catch (Exception ex)
{
}
}
private async void APITempSave(string json)
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
if (await tempFolder.TryGetItemAsync("APIData.json") != null)
{
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
else
{
StorageFile APIData = await tempFolder.CreateFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
}
private ActivationService CreateActivationService()
{
return new ActivationService(this, typeof(Views.VendorsPage), new Lazy<UIElement>(CreateShell));
}
private UIElement CreateShell()
{
return new Views.ShellPage();
}
}
}
I think what I need to do is call _activationService = new Lazy<ActivationService>(CreateActivationService); once APITempSave() has been called but I am unsure of how to do so and what best practices are.
Any guidance would be greatly appreciated!
After further investigation and familiarisation with the generated solution, as well as additional Googling of await, async, and Tasks<>, I was able to implement the request as a service alongside items such as ThemeSelector, and ToastNotifications.
The ThemeSelector is one of the first things to be called in order to determine light and dark theme mode for the current user, so I was able to model my service around it and call it at the same time.
This is obviously very specific to the code that template studio generates, but some concepts are shared and should anyone else look for similar answers in the future maybe they'll find this.
APIRequest.cs (Service)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
namespace Braytech_3.Services
{
public static class APIRequest
{
internal static async Task Request()
{
//Create an HTTP client object
Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
//Add a user-agent header to the GET request.
var headers = httpClient.DefaultRequestHeaders;
Uri requestUri = new Uri("https://json_url");
//Send the GET request asynchronously and retrieve the response as a string.
Windows.Web.Http.HttpResponseMessage httpResponse = new Windows.Web.Http.HttpResponseMessage();
string httpResponseBody = "";
try
{
//Send the GET request
httpResponse = await httpClient.GetAsync(requestUri);
httpResponse.EnsureSuccessStatusCode();
httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
await APITempSave(httpResponseBody);
}
catch (Exception ex)
{
}
}
internal static async Task APITempSave(string json)
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
if (await tempFolder.TryGetItemAsync("APIData.json") != null)
{
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
else
{
StorageFile APIData = await tempFolder.CreateFileAsync("APIData.json");
await FileIO.WriteTextAsync(APIData, json);
}
}
}
}
ActiviationService.cs (originally called by App.xaml.cs)
private async Task InitializeAsync()
{
await ThemeSelectorService.InitializeAsync();
await APIRequest.Request();
}

Passing generic functions with paramaters

I think I might be retarded or I'm asking too much out of C# but I can't get this to work.
What essentially I'm trying to do is to wrap an API-client with some logging functions and a method to request a new token from the API-server.
controller:
public class TestController : ApiController
{
[HttpGet]
[Route("api/model/get/{id}"]
public IHttpActionResult GetModel<Model>(int id)
{
var result = Service.DoHttp<Model>(ServiceClass.GetModel, id);
}
}
service:
public static class ServiceClass
{
private static readonly HttpClient client = new HttpClient() { BaseAddress = new Uri(Globals.ExternalApiPath) };
private static string TokenHeader = "";
public async static Task<HttpResponseMessage> GetModel(int id)
{
var response = client.GetAsync($"/api/get/{id}");
return await response;
}
public static T DoHttp<T>(Func<int, HttpResponseMessage> funk, int id)
{
try
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", TokenHeader);
var result = funk(id);
if (result.IsSuccessStatusCode)
{
return result.Content.ReadAsAsync<T>().Result;
}
else
{
throw new Exception(String.Format($"Unknown error! Unable to contact remote API! AccessToken: {TokenHeader} Status code: {result.StatusCode}"));
}
}
catch (Exception e)
{
// log ex
throw e;
}
}
}
But my Service.DoHttp(Service.GetModel, id); complains about it being the wrong return type.
What am I doing wrong or have I misunderstood the whole concept?
EDIT: Compiler complains about 'Task ServiceClass.GetModel(int)' has the wrong return type
Change the DoHttp method to the following.
public static T DoHttp<T>(Func<int, Task<HttpResponseMessage>> funk, int id)
As the GetModel method returns a Task you need to use a task as the return type of the Func too.

WebAPI Client -- handle my own exception rather then AggregateException

I have written a very simple WebApiClient extending HttpClient. The code is following. The main reason to do that was to throw MyOwnWebApiException when httpResponse.IsSuccessStatusCode is false.
public class WebApiClient : HttpClient
{
public WebApiClient(string apiBaseUrl)
{
this.BaseAddress = new Uri(apiBaseUrl);
this.DefaultRequestHeaders.Accept.Clear();
}
public void AddAcceptHeaders(MediaTypeWithQualityHeaderValue header)
{
this.DefaultRequestHeaders.Accept.Add(header);
}
public async Task<string> DoPost(string endPoint, Object dataToPost)
{
HttpResponseMessage httpResponse = await ((HttpClient)this).PostAsJsonAsync(endPoint, dataToPost);
if (httpResponse.IsSuccessStatusCode)
{
string rawResponse = await httpResponse.Content.ReadAsStringAsync();
return rawResponse;
}
else
{
string rawException = await httpResponse.Content.ReadAsStringAsync();
MyOwnWebApiErrorResponse exception =
JsonConvert.DeserializeObject<MyOwnApiErrorResponse>(rawException, GetJsonSerializerSettings());
throw new MyOwnWebApiException (exception.StatusCode,exception.Message,exception.DeveloperMessage,exception.HelpLink);
}
}
#region "Private Methods"
private static JsonSerializerSettings GetJsonSerializerSettings()
{
// Serializer Settings
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
return settings;
}
#endregion
Following is the code of the class using WebApiClient.
class TestWebApiClient
{
private WebApiClient _client;
public ComputationProcessesWebApiClient()
{
_client = new WebApiClient("http://test.api/");
_client.AddAcceptHeaders(new MediaTypeWithQualityHeaderValue("application/json"));
}
public void GetData(string dataFor)
{
try
{
DataRequest request = new DataRequest();
request.dataFor = dataFor;
**// THIS LINE IS THROWING AGGREGATEEXCEPTION--- **I WANT MyOwnException ****
string response = _client.DoPost("GetData", request).Result; // Use the End Point here ....
}
catch (MyOwnWebApiException exception)
{
//Handle exception here
}
}
}
Question
In the TestWebApiClient class, i dont want to catch AggregateException, rather i want to keep it more elegent and catch MyOwnWebApiException, but the problem is the line ** _client.DoPost("GetData", request).Result** throws an AggregateException if something goes wrong from the WebApi. How to change the code so that from TestWebApiClient i only have to catch MyOwnException ??
This is as a result of synchronously waiting for your task. If you stay async and await your task instead, you'll find that your actual Exception is the one that is caught.
Compare the following below:
void Main()
{
TryCatch();
TryCatchAsync();
}
void TryCatch()
{
try
{
ThrowAnError().Wait();
}
catch(Exception ex)
{
//AggregateException
Console.WriteLine(ex);
}
}
async Task TryCatchAsync()
{
try
{
await ThrowAnError();
}
catch(Exception ex)
{
//MyException
Console.WriteLine(ex);
}
}
async Task ThrowAnError()
{
await Task.Yield();
throw new MyException();
}
public class MyException:Exception{};
Top hint for async/await? It's async/await all the way down. The moment you .Wait() or .Result on a Task, things start to get messy.

Categories

Resources