Mocking HttpClient in xunit for different endpoint - c#

I am mocking HttpClient for unit testing following this SO suggestion. It works as expected for a single endpoint, and I am wondering how I can adjust it for a different endpoint. Please see the following sample:
class Agent
{
private HttpClient _client;
private string _baseUri = "http://example.com/";
public Agent(HttpClient client)
{ _client = client; }
public bool Run()
{
var res1 = client.GetAsync(new Uri(_baseUri, "endpoint1"));
var res2 = client.GetAsync(new Uri(_baseUri, "endpoint2"));
return res1 == res2
}
}
// In the test method:
var responseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_return")
};
var mock = new Mock<HttpMessageHandler>();
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage);
client = new HttpClient(mock.Object);
var agent = new Agent(client);
var resp = agent.Run();
Assert.True(resp)
In the above example, resp will be always true because as a result of mocking, the response from both endpoints in the Run method will be equal.

I think that you should set up two SendAsync calls as in the following.
// response to /endpoint1
var responseMessage1 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_return")
};
// response to /endpoint2
var responseMessage2 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_other_return")
};
var mock = new Mock<HttpMessageHandler>();
// mock a call to /endpoint1
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint1")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage1);
// mock a call to /endpoint2
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint2")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage2);
var client = new HttpClient(mock.Object);
var agent = new Agent(client);
var resp = agent.Run();

Related

Best way to make multiple calls to API endpoint

I have designed the following method to call api within the loop. I was wondering if there is any other better way (performance) of calling APIs multiple times?
private List<ClientInfo> populateDetails(List<ClientInfo> lstClientDetails)
{
using (var client = new HttpClient())
{
client.BaseAddress = EndpointAddress;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string Uri = "";
ClientInfo clientInfo;
foreach (var record in lstClientDetails)
{
Uri = string.Format("{0}", record.id);
var postResponse = client.GetAsync(Uri).Result;
if (postResponse.IsSuccessStatusCode)
{
string result = postResponse.Content.ReadAsStringAsync().Result;
clientInfo = JsonConvert.DeserializeObject<ClientInfo>(result);
if (clientInfo != null)
record.email = clientInfo.email;
}
}
return lstClientDetails;
}
}
Maybe you can change your code to use async/await pattern
private async Task populateDetails(List<ClientInfo> lstClientDetails)
{
using (var client = new HttpClient())
{
client.BaseAddress = EndpointAddress;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string Uri = "";
ClientInfo clientInfo;
foreach (var record in lstClientDetails)
{
Uri = string.Format("{0}", record.id);
var postResponse = await client.GetAsync(Uri);
if (postResponse.IsSuccessStatusCode)
{
string result = await postResponse.Content.ReadAsStringAsync();
clientInfo = JsonConvert.DeserializeObject<ClientInfo>(result);
if (clientInfo != null)
record.email = clientInfo.email;
}
}
}
}
And as you can see it doesn't make senso to return the same List which is passed as a paramters: the code will change the underlyne objects
Reference Microsoft Docs Parallel Library
sample code:
public class ParallelClient
{
private async Task ParallelRequest(List<string> requests)
{
var responses = new ConcurrentBag<HttpResponseMessage>();
using (var client = new HttpClient())
{
Parallel.ForEach(requests, new ParallelOptions { MaxDegreeOfParallelism = 10 }, async r =>
{
var requestMessage = new HttpRequestMessage();
requestMessage.Method = HttpMethod.Post;
requestMessage.Content = new StringContent(r, Encoding.UTF8, "application/json");
var response = await client.SendAsync(requestMessage);
responses.Add(response);
});
}
}
}

C# HttpClient return Status WaitingForActivation

I have only one method for Api Request as follow
private async Task<Site> getSiteAsync(string siteId)
{
Site site = null;
var response = await httpClient.SendAsync(
new HttpRequestMessage(HttpMethod.Get, httpClient.BaseAddress + $"api/sites/{siteId}"));
if (response.IsSuccessStatusCode)
{
var stream = await response.Content.ReadAsStreamAsync();
site = await JsonSerializer.DeserializeAsync<Site>(stream, serializerOptions);
}
return site;
}
When I try to call it from my MainClass the first call (a) works fine, but the b,c and d they all return me the Status = WaitingForActivation.
private readonly HttpClient httpClient = new HttpClient();
private readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions();
public MainWindow()
{
httpClient.BaseAddress = new Uri($"http://localhost:5000/MyApi/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var a = getSiteAsync("0001"); << I only get the value of this call.
var b = getSiteAsync("0002");
var c = getSiteAsync("0003");
var d = getSiteAsync("0004");
InitializeComponent();
}
How can I get the Result of b,c and d ?
You should await the calls and since you can only do this in an async method and not in a constructor, you should move your code to a method or an event handler:
public MainWindow()
{
httpClient.BaseAddress = new Uri($"http://localhost:5000/MyApi/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
Loaded += async (s, e) =>
{
var a = await getSiteAsync("0001");
var b = await getSiteAsync("0002");
var c = await getSiteAsync("0003");
var d = await getSiteAsync("0004");
};
InitializeComponent();
}

WebService Post is working on emulator but not on device

I'm trying to POST data to a WebService with SSL Certification.
It's working fine in Emulator but the Device is keeping stuck on GetResponseAsync(). I tried it with HttpClient with and without ModernHttpClient-Library but it stuck on PostAsync(), so it doesn't work too.
One Solution i tried of another Thread is just to add
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
^but it's also not working
In Manifest i added Permissions for Internet.
Thats the Code with GetResponseAsync()
public async Task<Response<V>> CallWebServiceAsync<T, V>(string ticket, string url, T requestData)
{
try
{
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
DataContractJsonSerializer serializer = new DataContractJsonSerializer(request.GetType(), this._knownTypes);
var httpRequest = WebRequest.CreateHttp(url);
httpRequest.Method = "POST";
httpRequest.UseDefaultCredentials = true;
httpRequest.ContentType = "application/json";
var requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false);
serializer.WriteObject(requestStream, request);
var response = await httpRequest.GetResponseAsync().ConfigureAwait(false);
var resultStream = response.GetResponseStream();
if (resultStream == null)
{
throw new FailedWebServiceInteractionException($"Got no response stream from request. URL:{url}");
}
var jsonResponseSerializer = new DataContractJsonSerializer(typeof(Response<V>), this._knownTypes);
Response<V> result = (Response<V>)jsonResponseSerializer.ReadObject(resultStream);
resultStream.Dispose();
return result;
}
catch (Exception e)
{
throw new FailedWebServiceInteractionException("Deserialization from json response failed", e);
}
}
Code with PostAsync() (+- ModernHttpClient)
public async Task<Response<V>> CallWebServiceAsync<T, V>(string ticket, string url, T requestData)
{
try
{
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
var httpClientHandler = new NativeMessageHandler
{
UseDefaultCredentials = true
};
var httpRequest = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(url)
};
httpRequest.DefaultRequestHeaders.Accept.Clear();
httpRequest.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = JsonConvert.SerializeObject(request);
var response =
await
httpRequest.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"))
.ConfigureAwait(false);
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<Response<V>>(jsonString);
}
catch (Exception e)
{
throw new FailedWebServiceInteractionException("Deserialization from json response failed", e);
}
}
I don't know if the certificates are the problem or something else.
They're not self-signed.
I found a solution. Time is gone since then. I don't know the exact solution but here is my code that works fine now:
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
var httpClientHandler = new OkHttpClientHandler
{
UseDefaultCredentials = true,
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ClientCertificateOptions = ClientCertificateOption.Automatic
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(url),
Timeout = TimeSpan.FromSeconds(15)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonRequest = this.SerializeToJson(request);
var compressedRequest = StringCompressor.CompressString(jsonRequest);
var httpRequest = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new ByteArrayContent(compressedRequest)
{
Headers = {ContentType = new MediaTypeHeaderValue("application/json")}
}
};
var httpResponse = await client.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
client.Dispose();
httpResponse.EnsureSuccessStatusCode();
var compressedResponse = await httpResponse.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
httpResponse.Dispose();
var jsonResponse = StringCompressor.DecompressString(compressedResponse);
var jsonResponseSerializer = new DataContractJsonSerializer(typeof(Response<V>), this._knownTypes);
Response<V> result;
using (var jsonResponseStream = this.GenerateStreamFromString(jsonResponse))
{
result = (Response<V>) jsonResponseSerializer.ReadObject(jsonResponseStream);
}
return result;
I'm using a self compiled wrapper of OkHttpClient library. I think ModernHttpClient (it is referencing OkHttp too) works too. Someone needs to test it.
Important to know is any other project that references this project need the library too.
App.Base Project - Implemented the above code to connect to
Webservice
App Project - References to App.Base. Need the library
too.

A task was cancelled error

I am using REST call, the code is like:
public bool restfunc(string id)
{
var result = Task.Factory.StartNew(() => RunAsync(id)).Result; //RunAsync(id).Wait();
return true;
}
public static Task RunAsync(string id)
{
try
{
dynamic result = null;
string pathValue = WebConfigurationManager.AppSettings["R2G2APIUrl"];
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(pathValue);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMinutes(5);
Task responsetask = null;
var jobid = id.Split('_')[1];
client.GetAsync("OnTranscriptionStarted/" + jobid).ContinueWith((requesttask) =>
{
responsetask = requesttask;
HttpResponseMessage resp = requesttask.Result; //HERE ERR OCCUR
resp.EnsureSuccessStatusCode();
resp.Content.ReadAsStringAsync().ContinueWith((readtask) =>
{
result = JsonConvert.DeserializeObject(readtask.Result);
});
});
}
return result;
Doubt:
I have API in pathvalue..I have to write pathvalue in
GetAsync() or function name and id which I want to call?
Help me out from this problem please..
You can use UriBuilder for that:
var builder = new UriBuilder(
"http", pathValue, 80, string.Format("OnTranscriptionStarted/{0}", jobId));
client.GetAsync(builder.Uri.ToString());

How to post data using HttpClient?

I have got this HttpClient from Nuget.
When I want to get data I do it this way:
var response = await httpClient.GetAsync(url);
var data = await response.Content.ReadAsStringAsync();
But the problem is that I don't know how to post data?
I have to send a post request and send these values inside it: comment="hello world" and questionId = 1. these can be a class's properties, I don't know.
Update I don't know how to add those values to HttpContent as post method needs it. httClient.Post(string, HttpContent);
You need to use:
await client.PostAsync(uri, content);
Something like that:
var comment = "hello world";
var questionId = 1;
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("comment", comment),
new KeyValuePair<string, string>("questionId", questionId)
});
var myHttpClient = new HttpClient();
var response = await myHttpClient.PostAsync(uri.ToString(), formContent);
And if you need to get the response after post, you should use:
var stringContent = await response.Content.ReadAsStringAsync();
Try to use this:
using (var handler = new HttpClientHandler() { CookieContainer = new CookieContainer() })
{
using (var client = new HttpClient(handler) { BaseAddress = new Uri("site.com") })
{
//add parameters on request
var body = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("test", "test"),
new KeyValuePair<string, string>("test1", "test1")
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "site.com");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded; charset=UTF-8"));
client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
client.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
client.DefaultRequestHeaders.Add("X-MicrosoftAjax", "Delta=true");
//client.DefaultRequestHeaders.Add("Accept", "*/*");
client.Timeout = TimeSpan.FromMilliseconds(10000);
var res = await client.PostAsync("", new FormUrlEncodedContent(body));
if (res.IsSuccessStatusCode)
{
var exec = await res.Content.ReadAsStringAsync();
Console.WriteLine(exec);
}
}
}
Use UploadStringAsync method:
WebClient webClient = new WebClient();
webClient.UploadStringCompleted += (s, e) =>
{
if (e.Error != null)
{
//handle your error here
}
else
{
//post was successful, so do what you need to do here
}
};
webClient.UploadStringAsync(new Uri(yourUri), UriKind.Absolute), "POST", yourParameters);

Categories