MSUnit Testing Async Hell - c#

I have an async Task unit test (MVC, c#, .NET 4.5.2). It does an await on a aysnc Task<ActionResult> method, which in turn has an await call on a async method.
The test, and others like it, will pass if I select them and choose Debug Selected Tests from the right-click menu in Visual Studio 2017.
The problem is when I select Run Selected Tests or Run All. It is then that many of the tests will fail if they follow the condition mentioned at the beginning. Any test that only returns a RedirectToRouteResult without having gone the aforementioned drill-down will pass.
[TestMethod]
public async Task TestPartsController_GetPartInfo_ReturnsInfo()
{
//arrange
PartController pc = new PartController();
//act
var result = await pc.GetPartInfo("PC123456");
//assert
Assert.IsIntanceOfType(result, typeof(ViewResult));
Assert.AreEqual("Form", ((ViewResult)result).ViewName);
Assert.AreEqual("PC123456", result.Model.PartNum.ToUpper());
}
public async Task<ActionResult> GetPartInfo(string partNum)
{
if (string.IsNullOrEmpty(partNum)
{
return RedirectToAction("Index")
}
var response = await ServiceClient.GetJsonAsync("/part/partinfo", "?partNum=" + partNum;
response.EnsureSuccessStatusCode();
results = await response.Content.ReadAsAsync<Dto.PartNumInfo>();
...
return View("Form", model);
}
public async Task<HttpResponseMessage> GetAsync(Controllers controller, string criteria)
{
HttpClient client;
string service = GetService(controller, out client);
var response = await client.GetAsync(service + criteria);
return response;
}
Solution
Use async/await all the way through as well as using statements and IDisposable.
public async Task<HttpResponseMessage> GetJsonAsync<T>(Controllers controller, T data)
{
HttpResponseMessage response;
using (var service = new MyService())
{
HttpClient http;
string serviceLoc = service.GetServiceClient(controller, out http);
response = await http.GetAsync(serviceLoc, data);
}
return response;
}

Solution Use async/await all the way through as well as using statements and IDisposable.
public async Task<HttpResponseMessage> GetJsonAsync<T>(Controllers controller, T data)
{
HttpResponseMessage response;
using (var service = new MyService())
{
HttpClient http;
string serviceLoc = service.GetServiceClient(controller, out http);
response = await http.GetAsync(serviceLoc, data);
}
return response;
}

Related

C# UnitTest Async Method Error - System.MissingMethodException: Method not found:

Hi I have a window services project that I already successfully implement to the server. But I have to create unit test which I stuck for weeks to solve it. Can any of you guys help me?
I have clean the project several time and the issue come out when I try to setup the Mock based on my interface
I also have refactoring the code several time but fail to run the
unittest :(
Here is my code:
Interface
public interface IJobScheduler
{
Task<HttpResponseMessage> GetASync(HttpClient client, string destination, CancellationTokenSource cancelToken);
Task<bool> RunJobAgent(HttpClient client);
}
Class (purposely create to inject data using unit test)
public class JobSchedular
{
private IJobScheduler iJobScheduler;
public JobSchedular(IJobScheduler ijobscheduler) => iJobScheduler = ijobscheduler;
public JobSchedular() => iJobScheduler = new JobSchedularSvc();
public async Task<HttpResponseMessage> GetASync(HttpClient client, string destination, CancellationTokenSource cancelToken)
{
Task<HttpResponseMessage> result = iJobScheduler.GetASync(client, destination, cancelToken);
return await result;
}
}
Actual Class
public partial class JobSchedularSvc : ServiceBase, IJobScheduler
{
public async Task<HttpResponseMessage> GetASync(HttpClient client, string destination, CancellationTokenSource cancelToken)
{
try
{
HttpResponseMessage response;// = new HttpResponseMessage();
using (client)
{
response = await client.GetAsync(destination, cancelToken.Token);
}
return response;
}
catch (Exception ex)
{
LogHandler.InsertLog(LogLevel.Error, $"FAILED: GetAsync() - {ex.Message}", ex.StackTrace, "JobSchedulerSvc", "JobSchedulerSvc", null, null, null, null).Wait();
return null;
}
}
}
Test Method
public async Task Test()
{
var message = new HttpResponseMessage(HttpStatusCode.OK);
JobScheduler = new Mock<IJobScheduler>();
JobScheduler.Setup(test => test.GetASync(It.IsAny<HttpClient>(), It.IsAny<string>(), It.IsAny<CancellationTokenSource>()))
.Returns(Task.FromResult(new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{'Result':true,'Exception':[]}") }));
CancellationTokenSource cancelToken = new CancellationTokenSource();
var response = new JobSchedular(JobScheduler.Object).GetASync(new HttpClient(), "/api/job/runjobagent", cancelToken);
var result = await response.Result.Content.ReadAsStringAsync();
Assert.AreEqual(result, "{'Result':true,'Exception':[]}");
}
I just want to call the local function GetAsync() which returns httpResponseMessage
I edit the .csproj file and add this line it works
<PropertyGroup>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>

Get & Post in ASP.NET Blazor

With the help of a few samples available on the internet, I am able to develop a ASP.NET Core Hosted Blazor Application.
But While Calling an api as follow
private async Task Refresh()
{
li.Clear();
li = await Http.GetJsonAsync<SampleModel[]>("/api/Sample/GetList");
StateHasChanged();
}
private async Task Save()
{
await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
await Refresh();
}
In the line below:
await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
How can I check status code of this HTTP call?
If there occurs any problem in API call than I want to display a message.
But when I do:
HttpResponseMessage resp = await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
Then it says:
can not cast void to HttpResponse Message
I am using below methods:
GetJsonAsync() // For HttpGet
SendJsonAsync() // For HttpPost And Put
DeleteAsync() // For HttpDelete
How can I verify the status code here ?
The thing is that you are using blazor's HttpClientJsonExtensions extensions,
Which internally usually calls
public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content);
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
{
var requestJson = JsonUtil.Serialize(content);
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
if (typeof(T) == typeof(IgnoreResponse))
{
return default;
}
else
{
var responseJson = await response.Content.ReadAsStringAsync();
return JsonUtil.Deserialize<T>(responseJson);
}
}
The GET requests use HttpContext.GetStringAsync internally
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
{
var responseJson = await httpClient.GetStringAsync(requestUri);
return JsonUtil.Deserialize<T>(responseJson);
}
while the normal HttpClient API still exists and can be used just as in those extension methods.
Those extension methods simply wrap the default HttpClient calls.
If you desire to have access to response status you would need to write your own wrappers that expose the desired functionality or just use the default API
Try this:
var response = await Http.SendJsonAsync <HttpResponseMessage>(HttpMethod.Post, "api/Sample/Add", obj);

No response from HttpResponseMessage

I have MVC project with service like below:
namespace comp.Services
{
public class CompService
{
public HttpClient client = new HttpClient();
public CompService()
{
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
protected async Task<string> GetProductAsync(string path)
{
var resp = "nothing here";
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
resp = await response.Content.ReadAsStringAsync();
}
return resp;
}
public string GetProduct(string path)
{
return GetProductAsync(path).GetAwaiter().GetResult();
}
}
}
and actionResult to view:
namespace comp.Controllers
{
public class HomeController : Controller
{
public CompService compService;
public HomeController()
{
compService = new CompService();
}
public ActionResult About()
{
var timeServer = compService.GetProduct("/api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
When in debuger I encounter this line:
HttpResponseMessage response = await client.GetAsync(path);
program exit from debuger and there is no respone in browser.
The same code written in console application works.
In VS output is message that response is succes:
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.RemoteDependency","time":"2018-02-18T13:48:31","tags":{"ai.internal.sdkVersion":"rddf:2.2.0-738","ai.internal.nodeName":"DESKTOP-xxxxx","ai.cloud.roleInstance":"DESKTOP-xxxxx"},"data":{"baseType":"RemoteDependencyData","baseData":{"ver":2,"name":"/api/v1/time","id":"xxxxx=","data":"https://api.xxx.com/api/v1/time","duration":"00:00:01.3150000","resultCode":"200","success":true,"type":"Http","target":"xxx","properties":{"DeveloperMode":"true"}}}}
In browser console output:
[14:51:33 GMT+0100 (Central European Standard Time)] Browser Link: Failed to send message to browser link server:
Error: SignalR: Connection must be started before data can be sent. Call .start() before .send()
Thanks for help.
You should really keep the code async all the way through in stead of trying to mix synchronous and asynchronous code
namespace comp.Services {
public class CompService {
static HttpClient client = new HttpClient();
static CompService() {
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<string> GetProductAsync(string path) {
var resp = string.Empty;
using(var response = await client.GetAsync(path)) {
if (response.IsSuccessStatusCode) {
resp = await response.Content.ReadAsStringAsync();
}
}
return resp;
}
}
}
The controller action should also be made async
namespace comp.Controllers {
public class HomeController : Controller {
private CompService compService;
public HomeController() {
compService = new CompService();
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
That said, the service should also be abstracted
public interface ICompService {
Task<string> GetProductAsync(string path)
}
public class CompService : ICompService {
//...code removed for brevity
}
and injected into the controller instead of creating it manually.
public class HomeController : Controller {
private ICompService compService;
public HomeController(ICompService compService) {
this.compService = compService;
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
Reference Async/Await - Best Practices in Asynchronous Programming
What you've got is a deadlock. Unlike console apps, ASP.Net applications run in a Synchronization Context. That context is captured when you block with GetResult(). Then, in GetProductAsync, you await on the context that is blocked. It cannot resume until GetResult is done which cannot resolve until the await is done.
#NKosi 's answer should resolve the problem, there is no reason for you to have any synchronous code.
For demonstration only
You can hack your code to work by explicitly allowing your await to run on a different context. You should not do this in production, it is not a fix. It can fail if someone maintaining the CompService is not careful.
To await against a different context change this:
var timeServer = await compService.GetProductAsync("api/time");
To this:
var timeServer = await compService.GetProductAsync("api/time").ConfigureAwait(false);
I mention this only to help you understand what is happening in your code. Don't "fix" it this way and move on.

Switching from sync to async APS.NET API controller

I have this C# controller which preforming a different SQL functions by string receive as input.
public HttpResponseMessage GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}
I'm trying to rebuild it in Async method:
First I change the SQL sync function to async without any problem:
public async Task<string> SQLasyncFunctionGET(string SQLString)
How do I change the GetFunction class to activate it in the Web API that I've built?
public async Task<IHttpActionResult> GetFunction(string SQLString)
{
var content = await ???????????????
return ok(content);
}
I'm not that familiar with this stack, so I don't remember if you can return Task<HttpResponseMessage> or not. But if you can, this should work:
public async Task<HttpResponseMessage> GetFunction(string SQLstring)
{
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(await SQLFunctions.SQLsyncFunctionGet(SQLstring), System.Text.Encoding.UTF8, "application/json")
};
return response;
}

Result of a async task is blocking

I have an issue with a task blocking when I try to retrieve it's result.
I have the following piece of code I want executed synchronously (which is why I'm looking for the result)
I would ignore the reason each call has to be made (legacy software that requires multiple calls through different layers)
the call seems to break down after it starts the task for the final call to be made in the PostCreateProfile, I can see this request never makes it any further than this.
if (CreateProfile(demographics).Result) // Task blocks here
{
//dothing
}
private async Task<bool> CreateProfile(Demographics demographics)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(demographics);
return await profileService.Create(createProfileBindingModel);
}
public async Task<bool> Create(CreateProfileBindingModel model)
{
HttpResponseMessage response = await profileServiceRequest.PostCreateProfile(rootURL, model);
return response.IsSuccessStatusCode;
}
public Task<HttpResponseMessage> PostCreateProfile(string url, CreateProfileBindingModel model)
{
HttpContent contents = SerialiseModelData(model);
var resultTask = client.PostAsync(url, contents);
return resultTask;
}
The request will reach its destination if I was to change CreateProfile to an async void like so:
private async void CreateProfile(AppointmentController controller)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(controller);
await profileService.Create(createProfileBindingModel);
}
But I can't return the bool I want to use from this.
Can anyone point out what I am doing wrong?
You should never call .Result on a async/await chain.
Whatever code that calls CreateProfile(demographics) needs to be async too so it can do
if (await CreateProfile(demographics))
{
//dothing
}
Also, if you can you really should put .ConfigureAwait(false) wherever it is logically possible.
if (await CreateProfile(demographics).ConfigureAwait(false)) // depending on what dothing is you may not want it here.
{
//dothing
}
private async Task<bool> CreateProfile(Demographics demographics)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(demographics);
return await profileService.Create(createProfileBindingModel).ConfigureAwait(false);
}
public async Task<bool> Create(CreateProfileBindingModel model)
{
HttpResponseMessage response = await profileServiceRequest.PostCreateProfile(rootURL, model).ConfigureAwait(false);
return response.IsSuccessStatusCode;
}
public Task<HttpResponseMessage> PostCreateProfile(string url, CreateProfileBindingModel model)
{
HttpContent contents = SerialiseModelData(model);
var resultTask = client.PostAsync(url, contents);
return resultTask;
}

Categories

Resources