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>
Related
I'm having a problem with async to sync implementation of HttpClient.
Id = 8, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
I know what I'm doing is probably a bad practice and it would be ideal to make all the path async, but that's a request that the company is making me, so I have to do like this.
Project is build in NET Standard 1.1, to be used as a NuGet package and to be compatible with Framework and Core as well.
Here's my main client construction...
private static HttpClient _client;
private static Uri _baseAddress;
private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{ DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore };
public Client() { }
private Client(string baseUrl, Config config)
{
_baseAddress = new Uri(baseUrl);
_client = new HttpClient { Timeout = TimeSpan.FromSeconds(config.Timeout) };
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("X-API-KEY", config.Token);
}
private Client _paymentClient;
private Client _mainClient;
public Client Create(bool payment, Config config = null)
{
if (!payment)
{
_mainClient = _mainClient ?? new Client("https://api.address.com/", config);
return _mainClient;
}
_paymentClient = _paymentClient ?? new Client("https://payment.address.com/", config);
return _paymentClient;
}
public void Dispose() => _client.Dispose();
private static async Task<T> Send<T>(HttpMethod method, string url, object data = null)
{
var uri = new UriBuilder(_baseAddress);
uri.Path += url;
var request = new HttpRequestMessage(method, uri.Uri);
if (data != null)
request.Content = new StringContent(JsonConvert.SerializeObject(data, _settings), Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
T result = default;
if (response.IsSuccessStatusCode)
{
if (response.Content.Headers.ContentType.MediaType == "application/json")
{
var responseObj = JsonConvert.DeserializeObject<Response<T>>(content, _settings);
if (responseObj.HasError)
throw new Safe2PayException(responseObj.ErrorCode, responseObj.Error);
responseObj.ResponseDetail = result;
}
}
else throw new Exception((int) response.StatusCode + "-" + response.StatusCode);
request.Dispose();
response.Dispose();
return result;
}
And the Send<T> method is supposed to be a general treatment to process the request and response, wrapped on generic calls like this:
internal Task<T> Get<T>(string url) => Send<T>(HttpMethod.Get, url);
//OR even async...
internal async Task<T> Get<T>(string url) => await Send<T>(HttpMethod.Get, url);
Which are called like this, to send and receive data..
private Client Client { get; }
public CheckoutRequest(Config config) => Client = new Client().Create(true, config);
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
My problem is that the client is always getting me a WaitingfForActivation or even Running or WaitingToRun, doesn't matter if I change it to...
Task.Run(() => Send<T>(HttpMethod.Get, url));
//or
Task.Run(() => Send<T>(HttpMethod.Get, url).Result);
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url));
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url).ConfigureAwait(false));
I've been trying to find what I'm doing wrong, tried to change all the awaits, but I'm not being sucessful with this, so any help will be very much appreciated.
I suspect your problem is here:
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
You didn't show your code for Post<T>(), but I assume it's also an async Task<T> method, which means response is a Task<T> and your code is basically doing this:
Start a task.
Return a description of the incomplete task.
When I assume this is really what you want:
Start the task.
Wait for the task to complete.
Return the result of the task.
Ideally, this should be an async method, and you can await the task:
public async Task<object> Credit(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
If you absolutely must wait for the task synchronously (there are very few reasons to need to) then you can use .GetAwaiter().GetResult():
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction).GetAwaiter().GetResult();
return response;
}
The main benefit of .GetAwaiter().GetResult() instead of .Result is that, in the case of exceptions, it will throw the actual exception instead of an AggregateException.
Also, you can make your Create() method static:
public static Client Create(bool payment, Config config = null)
Then you don't need to initialize the class just to call it:
public CheckoutRequest(Config config) => Client = Client.Create(true, config);
Update: If you want async and non-async versions of the same method, you can follow the same standard that Microsoft uses and name the async method with the Async suffix. The non-async version can just call the async version. For example:
public async Task<object> CreditAsync(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
public object Credit(Transaction transaction)
{
return CreditAsync(transaction).GetAwaiter().GetResult();
}
I'm trying to test a public method (Method A) that consumes an HttpClient to access an external API. This public method calls a private method (Method B) of the same class to get an Access Token that is required by the HttpClient of Method A to send the request. The problem I am having is that I am creating a mock of the HttpClientFactory interface in order to test the response of the Method A, but in order for Method B get the token it needs its own instance of HttpClient. Therefore, the mock instance created in the Test method will be used by the Method B as well, and it will fail trying to get the Access Token. The following code makes the scenario more clear.
Method to be tested (Method A):
public async Task<HttpResponseMessage> SendAsync(string requestUri, string siteName, int accountId)
{
try
{
var accessToken = await GetTokenAsync(siteName, accountId);
if (accessToken == null)
throw new ArgumentNullException("Error Sending request - Could not find an access token");
var request = new HttpRequestMessage(HttpMethod.Get, $"{accessToken.Api}{requestUri}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Accesstoken);
var httpClient = _httpClientFactory.CreateClient();
return await httpClient.SendAsync(request);
}
catch (Exception e)
{
throw new Exception("Error Sending request.", e);
}
}
Test Method:
[Fact]
public async Task ShouldReturnHttpResponseMessage_OnSendAsync()
{
//_jaClientMock.Setup(x => x.GetTokenAsync(It.IsAny<string>(), It.IsAny<int>())).Verifiable();
_appSettingsMock.Setup(x => x.Value)
.Returns(GetValidFakeAppSettings());
HttpResponseMessage expectedResponse = GetListOfContacts(HttpStatusCode.OK, false);
_httpClientFactoryMock.Setup(x => x.CreateClient())
.Returns(GetMockedHttpClient(expectedResponse));
var response = await _jaClient.SendAsync("someurl", "siteName", 1000);
response.IsSuccessStatusCode.ShouldBeTrue();
}
The private Method (Method B):
private async Task<AccessToken> GetTokenAsync(string siteName, int accountId)
{
try
{
if (_cache.TryGetValue(GetCacheKeyForToken(siteName, accountId), out AccessToken value))
return value;
....
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
accessToken = await response.Content.ReadAsAsync<AccessToken>();
}
.....
return accessToken;
}
catch (Exception e)
{
throw new Exception("Error Getting an Access Token.", e);
}
}
Any idea How I can test Method A?
There ain't no such thing as a free lunch - if one wants to unit-test some code with external dependencies, then each and every of those external dependencies has to be mocked.
Or one can go one step up the test pyramid to integration tests (though it is not our case, probably).
So, you could:
Either mock the Token response in the _httpClientFactory the same way you mock it for the SendAsync ( ..._httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(GetMockedHttpClient(expectedResponse));...)
Or reorganize code in such a manner that tokens are not retrieved directly from API - create some single-method ITokenProvider interface that will be a bit easier to mock.
public interface ITokenProvider
{
public async Task<AccessToken> GetTokenAsync(string siteName, int accountId);
}
...
public async Task<HttpResponseMessage> SendAsync(string requestUri, string siteName, int accountId)
{
try
{
var accessToken = await _tokenProvider.GetTokenAsync(siteName, accountId);
...
[Fact]
public async Task ShouldReturnHttpResponseMessage_OnSendAsync()
{
var tokenProviderMock = new Mock<ITokenProvider>()
.Setup(o => o.GetTokenAsync("siteName", 1000))
.Returns(Constants.AllowedToken);
_jaClient = new JaClient(tokenProviderMock.Object);...
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;
}
Using Vs 2017 community and azure.
I have a web app MVC5, that has this class.
public static class SchedulerHttpClient
{
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
private static HttpClient _Client = new HttpClient();
public static HttpClient Client{ get { return _Client; } }//TODO: validate
public static async Task MainAsync()
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string baseAddress = ConfigurationManager.AppSettings["BaseAddress"];
string token = await AcquireTokenBySPN(tenantId, clientId, clientSecret).ConfigureAwait(false);
_Client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); //TODO ssmith: const or localization
_Client.BaseAddress = new Uri(baseAddress);
}
private static async Task<string> AcquireTokenBySPN(string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ConfigurationManager.AppSettings["ARMResource"]),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
private static async Task<dynamic> HttpPost(string tenantId, string payload)
{
var address = String.Format(ConfigurationManager.AppSettings["TokenEndpoint"], tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await _Client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync());
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
This class is meant to spin up a Httpclient, contact azure get a token, and set the client with it, this way i can re-use with authorization.
Issue is when and how to call the class, currently I have tried the Global.asx, the HomeController Constructor method and the Index method.
public HomeController()
{
//Init();
}
public async void Init()
{
await SchedulerHttpClient.MainAsync().ConfigureAwait(false);
}
public async Task<ActionResult> Index()
{
Init();
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
The error i get is
[InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]
Is my static class implemented correctly? If so, how would i instantiate the client and then re-use across my app?
UPDATE #Stephen Cleary:
public async Task<ActionResult> Index()
{
await SchedulerHttpClient.ClientTask.ConfigureAwait(false);
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
static async Task MakeARMRequests()
{
const string ResourceGroup = "fakegrp";
// Create the resource group
// List the Web Apps and their host names
var client = await SchedulerHttpClient.ClientTask;
var response = await client.GetAsync(
$"/subscriptions/{Subscription}/resourceGroups/{ResourceGroup}/providers/Microsoft.Web/sites?api-version=2015-08-01");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
foreach (var app in json.value)
{
Console.WriteLine(app.name);
foreach (var hostname in app.properties.enabledHostNames)
{
Console.WriteLine(" " + hostname);
}
}
}
Here is the refactored class as per suggestion.
public static class SchedulerHttpClient
{
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
{
var client = new HttpClient();
await MainAsync(client).ConfigureAwait(false);
return client;
});
public static Task<HttpClient> ClientTask => _Client.Value;
private static async Task MainAsync(HttpClient client)
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string baseAddress = ConfigurationManager.AppSettings["BaseAddress"];
string token = await AcquireTokenBySPN(client, tenantId, clientId, clientSecret).ConfigureAwait(false);
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); //TODO ssmith: const or localization
client.BaseAddress = new Uri(baseAddress);
}
private static async Task<string> AcquireTokenBySPN(HttpClient client, string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ConfigurationManager.AppSettings["ARMResource"]),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(client, tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
private static async Task<dynamic> HttpPost(HttpClient client, string tenantId, string payload)
{
var address = String.Format(ConfigurationManager.AppSettings["TokenEndpoint"], tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
Your problem is due to async void. As noted in my intro to async on ASP.NET article:
When an asynchronous handler completes the request, but ASP.NET detects asynchronous work that hasn’t completed, you get an InvalidOperationException with the message, “An asynchronous module or handler completed while an asynchronous operation was still pending.” This is usually due to asynchronous code calling an async void method...
Also see my article on async best practices for other reasons to avoid async void.
In your case, you have a singleton resource that requires initialization, and that initialization must be asynchronous. You only want to start the initialization once, and all callers should share the initialization result, so a Lazy<T> seems appropriate. Since the initialization is asynchronous, it can be represented by a Task. Hence, a Lazy<Task>:
public static class SchedulerHttpClient
{
... // Same as above, but making MainAsync private.
public static readonly Lazy<Task> Initialize = new Lazy<Task>(() => MainAsync());
}
Usage:
public async Task<ActionResult> Index()
{
await SchedulerHttpClient.Initialize.Value.ConfigureAwait(false);
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
This will be enough to get your code working, but I would go a step further and refactor SchedulerHttpClient so that it only exposes the HttpClient after it has been initialized:
public static class SchedulerHttpClient
{
private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
{
var client = new HttpClient();
await MainAsync(client).ConfigureAwait(false);
return client;
});
public static Task<HttpClient> ClientTask => _Client.Value;
private static async Task MainAsync(HttpClient client) { ... }
private static async Task<string> AcquireTokenBySPN(HttpClient client, string tenantId, string clientId, string clientSecret) { ... }
private static async Task<dynamic> HttpPost(HttpClient client, string tenantId, string payload) { ... }
}
This forces your MakeARMRequests to await on SchedulerHttpClient.ClientTask instead of accessing the HttpClient directly, so you don't have to remember to do it in all your controller methods.
As a final note, you may want to "reset" the Lazy<T> if the initialization actually fails. That would complicate this homegrown solution sufficiently that I would recommend using my AsyncLazy<T> type instead (available on NuGet).
I'm integrating a 3rd part API using TDD, and so I am implementing a HttpClient wrapper interface that exposes the possible api calls and so on.
I want to test that the correct payload was sent in a post method, but when I try to read the string content from my injected fake HttpMessageHandler I get an ObjectDisposedException. Is there a better way to test this?
Test code:
[Fact]
public async void PostSignupRequest_RequestSent_PostedSerializedRequestAsContent()
{
var client = MakeOnboardingClient();
_fakeJsonSerializer.SerializedResult = "some json";
await client.PostSignupRequest(_someSignupRequest);
Assert.Equal("some json", await _fakeMessageHandler.Request.Content.ReadAsStringAsync());
}
My HttpMessageHandler spy/test double:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage Request;
public string ResponseContent = string.Empty;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Request = request;
return await Task.FromResult(new HttpResponseMessage
{
Content = new StringContent(ResponseContent)
});
}
}
Production code:
public async Task<SignupRequestResponse> PostSignupRequest(SignupRequest request)
{
var json = _jsonSerializer.Serialize(request);
await _httpClient.PostAsync(/* url */, new StringContent(json));
return null;
}
I've found a fix now. In my HttpMessageHandler fake I don't just save the Request now, I also explicitly save the content string (which can be extracted at that point since the HttpClient hasn't disposed the request yet). My fake now looks like this:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage Request;
public string LastRequestString = string.Empty;
public string ResponseContent = string.Empty;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Content != null) // needed this to prevent some NPEs in other tests, YMMV
{
LastRequestString = await request.Content.ReadAsStringAsync();
}
Request = request;
return await Task.FromResult(new HttpResponseMessage
{
Content = new StringContent(ResponseContent)
});
}
}