I'm going round in circles. So my WebApi returns the output it needs to the browser when i am running in debug. Firefox and IE display the List i need. However when i am trying to unit test the response using Mock and Autofac the HttpResponseMessage I am getting back has no Contents. I get the feeling it is returning on a different context or something like that. I am not totally sure, hence asking the question. I have stringed the below unit test together via googling and the Autofac documentation.
WebApiMethod (contained within InScrapController, _WebScrapSprocService is injected by Autofac in the constructor)
public HttpResponseMessage GetFormItemsByFormNumber(int FormNumber)
{
HttpResponseMessage response;
try
{
//response = Request.CreateResponse(HttpStatusCode.OK, _WebScrapSprocService.GetFormItemsByFormNumber(FormNumber),new MediaTypeHeaderValue("application/json"));
response = Request.CreateResponse(HttpStatusCode.OK, new MediaTypeHeaderValue("application/json"));
response.Content = new StringContent(JsonConvert.SerializeObject(_WebScrapSprocService.GetFormItemsByFormNumber(FormNumber)),Encoding.UTF8, "application/json");
} catch (Exception e)
{
response = Request.CreateResponse(HttpStatusCode.InternalServerError, new StringContent(e.Message), new MediaTypeHeaderValue("application/json"));
}
//Checking if bob knows anything about this...
string bob = response.Content.ReadAsStringAsync().Result;
return response;
}
Unit Test
public void GetFormItemsByFormNumber()
{
using (var mock = AutoMock.GetLoose())
{
var Service = mock.Mock<IWebScrapSprocService>().Setup(x => x.GetFormItemsByFormNumber(3392));
var service = mock.Create<InScrapController>();
service.Request = new HttpRequestMessage();
service.Request.SetConfiguration(new HttpConfiguration());
var HttpResponse = service.Request.CreateResponse(HttpStatusCode.OK, Service, new MediaTypeHeaderValue("application/json"));
var response = service.GetFormItemsByFormNumber(3392);
mock.Mock<IWebScrapSprocService>().Verify(x => x.GetFormItemsByFormNumber(3392));
Assert.AreEqual(HttpResponse, response);
}
}
Those responses are not going to be the same. Also that method under test should be refactored. You are also not getting back any content because you have not setup the service to return anything.
public IHttpActionResult GetFormItemsByFormNumber(int FormNumber) {
IHttpActionResult response;
try {
var result = _WebScrapSprocService.GetFormItemsByFormNumber(FormNumber);
response = Ok(result);
} catch (Exception e) {
response = InternalServerError(e);
}
return response;
}
Next update the test
public void GetFormItemsByFormNumber() {
using (var mock = AutoMock.GetLoose()) {
// Arrange.
var formNumber = 3392;
var items = new List<FormItemsByFormNumber> {
new FormItemsByFormNumber {
//Populate as needed
},
new FormItemsByFormNumber {
//Populate as needed
},
//...etc
};
var serviceMock = mock.Mock<IWebScrapSprocService>();
serviceMock.Setup(x => x.GetFormItemsByFormNumber(formNumber)) // Calling this...
.Returns(items) // should return some value...
.Verifiable(); // and I want to verify that it was called.
var sut = mock.Create<InScrapController>();
// Act.
var response = sut.GetFormItemsByFormNumber(formNumber) as OkNegotiatedContentResult<List<FormItemsByFormNumber>>();
// Assert.
serviceMock.Verify(); //verify setups were exercised as expected.
Assert.IsNotNull(response);
}
}
Related
I have a method in service layer to connect to a service and i am using IHttpClientFactory. My method is working fine. Now i am trying to write unit test cases for the same.
public async Task<MyObject> MyMethodAsync(string arg1, string arg2)
{
var client = _httpClientFactory.CreateClient("XYZ");
var Authkey = "abc";
var AuthToken = "def";
var headers = new Dictionary<string, string>
{
{ Authkey,AuthToken }
};
client.AddTokenToHeader(headers); //This method set the DefaultRequestheader from the dictionary object
var reqData = new
{
prop1 = "X",
prop2 = "Y"
};//req object
var content = new StringContent(JsonConvert.SerializeObject(reqData), Encoding.UTF8, "application/json");
//This is httpClient Post call for posting data
HttpResponseMessage response = await client.PostAsync("postData", content);
if (!response.IsSuccessStatusCode || response.Content == null)
{
return null;
}
MyObject myObject = JsonConvert.DeserializeObject<MyObject>(response.Content.ReadAsStringAsync().Result);//read the result to an object
return myObject;
}
For the above method i am writing test cases. Here i am trying to set the Post methods out put a status code OK and expecting the
MyMethodAsync method to be true as the PostAsync is true. Here i am getting an exception
System.InvalidOperationException : An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
[Test]
public async Task MyMethodAsync_Gets_True()
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("It worked!")
};
//Mock the httpclientfactory
var _httpMessageHandler = new Mock<HttpMessageHandler>();
var mockFactory = new Mock<IHttpClientFactory>();
//Specify here the http method as post
_httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Post),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
});
var httpClient = new HttpClient(_httpMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
var arg1 = "X";
var arg2 = "D101";
var service = new MyService(_mockAppSettings.Object, mockFactory.Object);
var result = await service.MyMethodAsync(arg1, arg2);
// Assert
Assert.IsNotNull(result);
}
Can someone show what mistake i am doing here?
As the exception says you have to
either call the PostAsync with an absolute url
or set the BaseAddress of the HttpClient
If you choose the second one all you need to do is this:
var httpClient = new HttpClient(_httpMessageHandler.Object);
httpClient.BaseAddress = new Uri("http://nonexisting.domain"); //New code
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
With this modification the exception will be gone.
But your test will fail because the response.Content will be null and that's why MyMethodAsync will return with null.
To fix this let's change the Setup to this:
public static async Task MyMethodAsync_Gets_True()
{
//Arrange
MyObject resultObject = new MyObject();
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(resultObject))
};
var _httpMessageHandler = new Mock<HttpMessageHandler>();
_httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Post),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(response);
...
//Act
var result = await service.MyMethodAsync(arg1, arg2);
//Assert
Assert.NotNull(result);
}
I send a request to the API and sometimes receive the response with an HTTP 429 status code (TooManyRequests).
On average, for 10 requests, 2 will return 429 response and the remaining 8 return the correct value.
It also happened to me that when it was the first request (so there is no option for TooManyRequests)
public static List<ResponseObject> GetProductsFromRestAPI(int[] ProductIdArray )
{
List<ResponseObject> products = new List<ResponseObject>();
string action;
for (int i = 0; i < ProductIdArray.Length; i++)
{
action = "products/" + ProductIdArray[i].ToString();
client = AddHeadersToClient(action, new RestClient("https://api.usedbythiscode.com/")); //required by this API.
var currentProduct = RequestsLib.GetProduct(client, action);
products.Add(currentProduct);
}
return products;
}
public static Product GetProduct(RestClient restClient, string action) //todo test this for bugs
{
var result = new Product();
var request = new RestRequest(action, Method.GET);
var response = SendRequest(restClient, request);//Here I sometimes get response with 429.
//.. Other code
return result;
}
public static async Task<IRestResponse> SendRequest(RestClient restClient, RestRequest request)
{
return await restClient.ExecuteGetAsync(request);
}
Temporarily resolved it by sending another request with do while loop and usually second request return right answer.
do
{
SendRequest(restClient, request);
}
while (StatusCode != 200);
Where could the cause of the error lie?
Is it possible that I have unclosed requests?
Is creating multiple RestSharp clients a good practice?
EDIT:
The problem was on the server side. All I had to do was report the bug to the admins who provided the API. Thank you for help.
429 is Too Many Requests. Most APIs have some kind of rate-limiting in place so that a single client can't take down their server with too many requests.
The proper response for 429 is to retry. I recommend using Polly for retry logic, but be aware that HandleTransientHttpError doesn't consider 429 a transient error.
I agree with #mason, you should use async method with Task<> and await response Here is the part of login side of my mobileApp-project in Xamarin. You may want to see how to use async with Task<> easily.
public async Task<BSUser> ValidateUser(string userName, string password)
{
string url = Xamarin.Essentials.Preferences.Get(Constants.URL_KEY, "") + "/api/Validateuser";
HttpClient _Client = new HttpClient();
var data = new Dictionary<string, string>
{
{"userName", userName},
{"password", password}
};
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage httpResponse = await _Client.PostAsync(url, content);
if (httpResponse.IsSuccessStatusCode)
{
try {
var responseData = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject(responseData).ToString();
UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(result);
BSUser value = new BSUser();
value.UserName = userInfo.userCode;
return value;
}
catch (Java.Net.SocketException e)
{
Console.WriteLine("Hata", e);
return null;
}
}
else
{
return null;
}
}
catch (SystemException)
{
return null;
}
}
public async Task<>GetLandProperty(string num)
{
var request = new HttpRequestMessage(HttpMethod.Get, _httpClient.BaseAddress+relativeurl);
// added required headers here.
var response = await _httpClient.SendAsync(request);
}
now here response is httpresponsemessage. i dont understand how to extract data out of response and deserialize it. probably i can create class, fetch response's content and deserialise it but what to do if it return failure response or exception. how to return generic type in this case
This is related to your api code. You can catch the exception information and return different information by setting the HttpStatusCode of HttpResponseMessage in the api side.
In the GetLandProperty method, you can set the return type to object to return different content.
Determine the returned content by judging the status of IsSuccessStatusCode of response. If the status is true, use the type returned by deserialize and return the corresponding object. If it is false, return the response directly.
public async Task<object> GetLandProperty()
{
Product product = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("url");
var request = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string responseString = response.Content.ReadAsStringAsync().Result;
Newtonsoft.Json.Linq.JObject json = Newtonsoft.Json.Linq.JObject.Parse(responseString);
product = Newtonsoft.Json.JsonConvert.DeserializeObject<Product>(responseString);
return product;
}
return response;
}
}
My api code:
[HttpGet]
public HttpResponseMessage Get(int id)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
Product persona = _context.Product.FirstOrDefault(p => p.DataID == id);
if (persona != null)
{
response.Content = new StringContent(JsonConvert.SerializeObject(persona));
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
else
{
response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Content = new StringContent("error message");
return response;
}
}
Update
Another method to receive GetLandProperty method returned content:
public async Task<object> CallTest()
{
var obj = await GetLandProperty();
if(obj is Product)
{
Product product= (Product)obj;
// save data to db
}
else
{
//do other operations
return obj;
}
}
Here is the test result by postman:
I am not very sure whats the question.
To get response data
var result = await response.Content.ReadAsStringAsync();
and to desriazile it
var json= JsonConvert.DeserializeObject<T>(result);
I am trying to send a simple BsonDocument to WebAPI through PutAsync request.
However, the values of the BsonDocument missing when passed to the WebAPI Controller.
Here are the codes of my client.
public static async Task<HttpResponseMessage> sendBdoc()
{
try
{
var abc = new BsonDocument("Abc","hoho");
var bformatter = new BsonMediaTypeFormatter();
var id = ObjectId.GenerateNewId();
HttpClient client = new HttpClient()
client.BaseAddress = new Uri("http://localhost:58836");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson"));
var response = await client.PutAsync($"api/query/{id}", abc, bformatter);
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
Here are the codes of my controller:
public async Task<IHttpActionResult> Put(string id, BsonDocument doc)
{
try
{
//MongoDB Connection stuff here
var filter = new BsonDocument("_id", ObjectId.Parse(id));
var update = collection.ReplaceOneAsync(filter, doc, new UpdateOptions() { IsUpsert = true });
var result = await update;
if (result.IsAcknowledged)
{
return Ok();
}
return NotFound();
}
catch (Exception e)
{
return InternalServerError();
}
}
Screenshot of debugging my client app, variable abc is filled with value.
https://i.stack.imgur.com/txp8l.png
Screenshot of debugging at web controller side, value of the BsonDocument turned null now.
https://i.stack.imgur.com/lBTe3.png
Do i overlook something? Thanks alot
It could be a limitation of the default model binder.
Could you try to remove the "string id" from the Put method and resend the request?
I am calling an API Post method, however, I am not sure what I am doing wrong but the value in the API is always null. The method I am calling the API from is below. When I hit this I can see Ids is list of ints with 5 values for example.
private void Save(List<int> Ids)
{
var myAPI = ConfigurationManager.AppSettings["MyAPI"];
string myIds = string.Join(",", Ids);
using (var client = new HttpClient())
{
int result = client.PostAsync(myAPI, new { test = myIds }, new JsonMediaTypeFormatter())
.Result
.Content
.ReadAsAsync<int>()
.Result;
}
}
My API signature is like below - with a breakpoint on I can see it is getting hit but test the parameter I am trying to pass is always null
[HttpPost]
[Route("api/MyController/SaveData")]
public HttpResponseMessage SaveData([FromBody]List<string> test)
{
try
{
//Rest of method removed for brevity
I have tried removing the [FromBody] Annotation from the WebAPI controller but test still is getting null with breakpoint in the SaveData API method
Try this:
private void Save(List<int> Ids)
{
var myAPI = ConfigurationManager.AppSettings["MyAPI"];
using (var client = new HttpClient())
{
var requestBody = JsonConvert.SerializeObject(Ids);
var postRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = client.PostAsync(myAPI, postRequest).GetAwaiter().GetResult();
var rawResponse = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
// Do something with the answer
}
}
I also suggest to make the method private Task Save and replace .GetAwaiter().GetResult(); with await in front of that calls.
In my case i used System.Web.Http.ApiController instead of System.Web.Mvc.Controller. So over all code looks like
public class YourAppController : ApiController
{
[System.Web.Http.Route("publish-message")]
public HttpResponseMessage Post([System.Web.Http.FromBody] string msges)
{
//Your Code
return Request.CreateResponse(HttpStatusCode.OK, "");
}
}
public async Task<string> PublishMessageCall(string publishMessage){
var returnval = "";
string httpWebRqst = "http://localhost:543134535/publish-message";
using (HttpClient myClient = new HttpClient())
{
var jsonString = JsonConvert.SerializeObject(publishMessage);
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
var response = await myClient.PostAsync(httpWebRqst, content);
var responseString = await response.Content.ReadAsStringAsync();
}
return await Task.FromResult(returnval);}