Test controller action that makes an external API call - asp.net-mvc

I have an API controller with an action function. This function makes an external call for another API to get some data. this external call is made by simply creating a client with a URL. I want to create a test using WebApplicationFactory to test this action function.
I would like to know how to configure this external call. To say if the server calls this URL return this response.
May be it should be somewhere in overriding ConfigureWebHost to tell the server that if you call this URL (The external API url) return this response.
Here is the controller action I want to test.
namespace MyAppAPI.Controllers
{
public class MyController : ControllerBase
{
[HttpPost("MyAction")]
public async Task MyAction([FromBody] int inputParam)
{
var externalApiURL = "http://www.external.com?param=inputParam";
var client = new HttpClient();
var externalResponse = await client.GetAsync(externalApiURL);
//more work with the externalResponse
}
}
}
Here is the Test class I want to use
public class MyAppAPITests : IClassFixture<WebApplicationFactory<MyAppAPI.Startup>>
{
private readonly WebApplicationFactory<MyAppAPI.Startup> _factory;
public MyAppAPITests(WebApplicationFactory<MyAppAPI.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task Test_MyActionReturnsExpectedResponse()
{
//Arrange Code
//Act
//Here I would like to have something like this or a similar fashion
_factory.ConfigureReponseForURL("http://www.external.com?param=inputParam",
response => {
response.Response = "ExpectedResponse";
});
//Assert Code
}
}
The code in Test_MyActionReturnsExpectedResponse does not exist anywhere, it is just what I would like to have either by inheriting WebApplicationFactory or by configuring it. I would like to know how that can be achieved. i.e. configuring a response when an API controller makes an external call.
Thank you for your help.

The issue is that you have a hidden dependency, namely HttpClient. Because you're newing this up in your action, it's impossible to mock. Instead, you should be injecting this dependency into your controller. With ASP.NET Core 2.1+ that's possible with HttpClient thanks to IHttpClientFactory. However, out of the box, you cannot inject HttpClient directly into a controller, because controllers are not registered in the service collection. While you can change that, the recommended approach is to instead create a "service" class. This is actually better anyways as it abstract the knowledge of interacting with this API out of your controller entirely. Long and short, you should do something like:
public class ExternalApiService
{
private readonly HttpClient _httpClient;
public ExternalApiService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public Task<ExternalReponseType> GetExternalResponseAsync(int inputParam) =>
_httpClient.GetAsync($"/endpoint?param={inputParam}");
}
Then, register this in ConfigureServices:
services.AddHttpClient<ExternalApiService>(c =>
{
c.BaseAddress = new Uri("http://www.external.com");
});
And finally, inject it into your controller:
public class MyController : ControllerBase
{
private readonly ExternalApiService _externalApi;
public MyController(ExternalApiService externalApi)
{
_externalApi = externalApi;
}
[HttpPost("MyAction")]
public async Task MyAction([FromBody] int inputParam)
{
var externalResponse = await _externalApi.GetExternalResponseAsync(inputParam);
//more work with the externalResponse
}
}
Now, the logic for working with this API is abstracted from your controller and you have a dependency you can easily mock. Since you're wanting to do integration testing, you'll need to sub in a different service implementation when testing. For that, I'd actually do a little further abstraction. First, create an interface for the ExternalApiService and make the service implement that. Then, in your test project you can create an alternate implementation that bypasses HttpClient entirely and just returns pre-made responses. Then, while not strictly necessary, I'd create an IServiceCollection extension to abstract the AddHttpClient call, allowing you to reuse this logic without repeating yourself:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddExternalApiService<TImplementation>(this IServiceCollection services, string baseAddress)
where TImplementation : class, IExternalApiService
{
services.AddHttpClient<IExternalApiService, TImplementation>(c =>
{
c.BaseAddress = new Uri(baseAddress)
});
return services;
}
}
Which you would then use like:
services.AddExternalApiService<ExternalApiService>("http://www.external.com");
The base address could (and probably should) be provided via config, for an extra layer of abstraction/testability. Finally, you should be use a TestStartup with WebApplicationFactory. It makes it far easier to switch out services and other implementations without rewriting all your ConfigureServices logic in Startup, which of course adds variables to your test: e.g. is it not working because I forgot to register something the same way as in my real Startup?
Simply add some virtual methods to your Startup class and then use these for things like adding your databases, and here, adding your service:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
AddExternalApiService(services);
}
protected virtual void AddExternalApiService(IServiceCollection services)
{
services.AddExternalApiService<ExternalApiService>("http://www.external.com");
}
}
Then, in your test project, you can derive from Startup and override this and similar methods:
public class TestStartup : MyAppAPI.Startup
{
protected override void AddExternalApiService(IServiceCollection services)
{
// sub in your test `IExternalApiService` implementation
services.AddExternalApiService<TestExternalApiService>("http://www.external.com");
}
}
Finally, when getting your test client:
var client = _factory.WithWebHostBuilder(b => b.UseStartup<TestStartup>()).CreateClient();
The actual WebApplicationFactory still uses MyAppAPI.Startup, since that generic type param corresponds to the app entry point, not actually what Startup class is being used.

I think best way - i to use interfaces and MOCK's. Implement interface by inheritance of HttpClient and at the test mock this interface:
public interface IHttpClientMockable
{
Task<string> GetStringAsync(string requestUri);
Task<string> GetStringAsync(Uri requestUri);
Task<byte[]> GetByteArrayAsync(string requestUri);
Task<byte[]> GetByteArrayAsync(Uri requestUri);
Task<Stream> GetStreamAsync(string requestUri);
Task<Stream> GetStreamAsync(Uri requestUri);
Task<HttpResponseMessage> GetAsync(string requestUri);
Task<HttpResponseMessage> GetAsync(Uri requestUri);
Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption);
Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption);
Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken);
Task<HttpResponseMessage> GetAsync(Uri requestUri, CancellationToken cancellationToken);
Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content);
Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken);
Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content);
Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content);
Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken);
Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
Task<HttpResponseMessage> DeleteAsync(string requestUri);
Task<HttpResponseMessage> DeleteAsync(Uri requestUri);
Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken);
Task<HttpResponseMessage> DeleteAsync(Uri requestUri, CancellationToken cancellationToken);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);
void CancelPendingRequests();
HttpRequestHeaders DefaultRequestHeaders { get; }
Uri BaseAddress { get; set; }
TimeSpan Timeout { get; set; }
long MaxResponseContentBufferSize { get; set; }
void Dispose();
}
public class HttpClientMockable: HttpClient, IHttpClientMockable
{
}

Related

Blazor .NET Core 6 OData 8: Post handler in controller receives null object

I have a ASP.NET Core 6 WAsm Client-Server app using OData 8. The client is posting new data to the controller. The proper controller method is indeed being called, the json payload sent by the http client seems ok, yet the controller's post method's data object parameter is null. Why is that and what do I need to fix to make this work?
Here are the relevant code pieces:
Database
public class DS2DbContext : DbContext
{
public DbSet<WatermarkProperties> Watermarks { get; set; }
public DS2DbContext(DbContextOptions<DS2DbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
EDM Model
static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<WatermarkProperties>("Watermarks");
return builder.GetEdmModel();
}
HTTP call to server side controller
async Task<bool> UpdateWatermark()
{
Cloning.CopyProperties (editBuffer, Watermark);
HttpResponseMessage response;
string jsonData = JsonSerializer.Serialize(Watermark);
var stringData = new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = await _httpClient.PostAsync($"DocServer2/Watermarks", stringData);
return (response.StatusCode == System.Net.HttpStatusCode.OK);
}
HTTP payload
Relevant parts of controller
public class WatermarksController : ODataController
{
private readonly DS2DbContext _db;
private readonly ILogger<WatermarksController> _logger;
public WatermarksController(DS2DbContext dbContext, ILogger<WatermarksController> logger)
{
_logger = logger;
_db = dbContext;
}
[EnableQuery]
public async Task<IActionResult> Post([FromBody] WatermarkProperties watermark)
{
_db.Watermarks.Add(watermark);
await _db.SaveChangesAsync();
return Created(watermark.ID.ToString(), watermark);
}
}
"Watermark" is always null in WatermarksController.Post() ... ?

How to get different response type for the same request model using MediatR?

I am trying to understand how MediatR works. Never used this library before. Below code is not actual production code. It is only for understanding purpose.
Lets say I have two RequestHandlers. Each handler takes ProductModel as request but returns different type of response.
public class GetOrdersHandler : IRequestHandler<ProductModel, IEnumerable<Order>>
{
private readonly FakeDataStore _fakeDataStore;
public GetOrdersHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<IEnumerable<Order>> Handle(ProductModel request,CancellationToken cancellationToken
{
return await _fakeDataStore.GetAllOrdersForProduct(request);
}
}
public class SaveProductHandler : IRequestHandler<ProductModel, Product>
{
private readonly FakeDataStore _fakeDataStore;
public SaveProductHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<Product> Handle(ProductModel request,CancellationToken cancellationToken)
{
return await _fakeDataStore.SaveProduct(request);
}
}
Then in the same controller I have two action methods
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<ActionResult> GetAllOrders(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
[HttpPost]
public async Task<ActionResult> SaveProduct(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
}
Based on the MediatR code this may not work. Looks like the Send method creates instance of handler based on Request type. It keeps dictionary of RequestType and corresponding handler.
If my assumption is correct then does that mean I will have to create unique request model for each action method that will be using Send method?

Has DevExtreme DxDataGrid support for OData in Blazor?

I'm trying to test this feature, I see in the documentation how to use OData in Razor, but not in Blazor. It's OData suported?
I was trying the same thing, and apparently is not supported out of the box, but there's a workaround:
// Step 1: Add the nuget package Microsoft.OData.Client to the Blazor project.
// Step 2: Implement ODataAsyncAdapter
public class ODataAsyncAdapter : DevExtreme.AspNet.Data.Async.IAsyncAdapter
{
private HttpClient httpClient = new HttpClient();
public Task<int> CountAsync(IQueryProvider queryProvider, Expression expr, CancellationToken cancellationToken)
{
DataServiceQueryProvider provider = (DataServiceQueryProvider)queryProvider;
DataServiceQuery query = (DataServiceQuery)provider.CreateQuery(expr);
return this.httpClient.GetFromJsonAsync<int>(query.RequestUri.ToString(), cancellationToken);
}
public Task<IEnumerable<T>> ToEnumerableAsync<T>(IQueryProvider queryProvider, Expression expr, CancellationToken cancellationToken)
{
DataServiceQueryProvider provider = (DataServiceQueryProvider)queryProvider;
return ((DataServiceQuery<T>)provider.CreateQuery<T>(expr)).ExecuteAsync(cancellationToken);
}
}
// Step 3: Register the ODataAsyncAdapter
public static async Task Main(string[] args)
{
DevExtreme.AspNet.Data.Async.CustomAsyncAdapters.RegisterAdapter(typeof(DataServiceQueryProvider), new ODataAsyncAdapter());
}
// Final Step: In the Grid.razor page
<DxDataGrid T="#UserDto" CustomData="#this.LoadCustomData" ShowFilterRow="true" PageSize="10" KeyFieldName="#nameof(UserDto.Id)">
<Columns>
<DxDataGridColumn Field="#nameof(UserDto.Id)" />
<DxDataGridColumn Field="#nameof(UserDto.Name)" />
</Columns>
</DxDataGrid>
#code
{
protected async Task<LoadResult> LoadCustomData(DataSourceLoadOptionsBase options, CancellationToken cancellationToken)
{
options.AllowAsyncOverSync = false;
return await DataSourceLoader.LoadAsync(this.ODataService.CreateQuery<UserDto>("UserDto"), options, cancellationToken);
}
}
PS: I will try to simplify somethings, them I'll edit this anwser.

How to cancel IMvxAsyncCommand?

I just started using xamarin android with MVVMcross platform. I need to cancel an IMvxAsyncCommand but I try a lot of way, it still not work. Does anyone have any ideas or sample of this?
You can actually cancel a MvxAsyncCommand directly without creating any additional CancellationTokenSource.
Just call Cancel() on the object itself.
So instead of having the property being ICommand or IMvxCommand use MvxAsyncCommand or IMvxAsyncCommand.
This way when you have a CancellationToken as argument in your Task and you call Cancel() on the command, it will cancel that token.
private async Task MyAsyncStuff(CancellationToken ct)
{
await abc.AwesomeAsyncMethod(ct);
// and/or
ct.ThrowIfCancellationRequested();
}
So if you modify #fmaccaroni's answer, you will get this:
public class MyClass
{
public MyClass()
{
MyAsyncCommand = new MvxAsyncCommand(MyAsyncStuff);
CancelMyTaskCommand = new MvxCommand(() => MyAsyncCommand.Cancel());
}
public IMvxAsyncCommand MyAsyncCommand {get; private set; }
public ICommand CancelMyTaskCommand { get; private set; }
private async Task MyAsyncStuff(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await foo.BlablaAsync();
cancellationToken.ThrowIfCancellationRequested();
await bar.WhateverAsync();
cancellationToken.ThrowIfCancellationRequested();
await foo2.AnotherAsync();
}
}
EDIT: or even better if your async methods also allow passing along the CancellationToken cancelling them directly, the MyAsyncStuff method could be written like:
private async Task MyAsyncStuff(CancellationToken cancellationToken)
{
await foo.BlablaAsync(cancellationToken);
await bar.WhateverAsync(cancellationToken);
await foo2.AnotherAsync(cancellationToken);
}
Or if you don't care which order they get started and complete in:
private Task MyAsyncStuff(CancellationToken cancellationToken)
{
return Task.WhenAll(
foo.BlablaAsync(cancellationToken),
bar.WhateverAsync(cancellationToken),
foo2.AnotherAsync(cancellationToken));
}
So basically a IMvxAsyncCommand just calls a Task like this:
MyCommand = new MvxAsyncCommand(MyTask);
...
private async Task MyTask()
{
// do async stuff
}
In order to cancel a Task you need to use a CancellationToken, which you can get it from a CancellationTokenSource and use another command or whatever you want to call the Cancel() on the CancellationTokenSource, so one way you could do it would be:
public class MyClass
{
private CancellationTokenSource cts;
public MyClass()
{
MyAsyncCommand = new MvxAsyncCommand(MyTask);
CancelMyTaskCommand = new MvxCommand(() => cts.Cancel());
}
public ICommand MyAsyncCommand {get; private set; }
public ICommand CancelMyTaskCommand { get; private set; }
private async Task MyTask()
{
cts = new CancellationTokenSource();
await MyAsyncStuff(cts.CancellationToken);
cts.Dispose();
cts = null;
}
private async Task MyAsyncStuff(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await foo.BlablaAsync();
cancellationToken.ThrowIfCancellationRequested();
await bar.WhateverAsync();
cancellationToken.ThrowIfCancellationRequested();
await foo2.AnotherAsync();
}
}
You can then improve the code to avoid duplicate calls to the Task, handle the cancellation exception, use an MvxNotifyTask and many other things but this is the basics.
More info in Cancellation in Managed Threads
HIH

Asp.net MVC Control execute 2 async task

I want to check if this possible, I have an MVC Controller Action method (POST) which should have 2 parallel tasks, completing one of them should return the view based on the result from task that has completed.
Task is not aweb service which has asynchronous options. Assume its more like creating a bunch of file. I really want my task as simple as this.
private static string DoStuff(int sleepTime, string input)
{
Thread.Sleep(sleepTime);
return input;
}
Task would be like - but i dont if this is correct or not because there is not 'await'
public async Task<string> DelayTimerCallAsync()
{
string msgs = DoStuff(Convert.ToInt32(
ConfigurationManager.AppSettings["DelayTimer"]),
"Timeout");
return msgs;
}
Let me know if my question is not clear or if you need more information.
From below update my 2 tasks are like this
public Task<String> DelayTimerCallAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
String messages = null;
return DelayTimer();
}, cancellationToken);
}
public Task<String> BlazeCallAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
String messages = null;
return BlazeCall();
}, cancellationToken);
}
How can I call them and use Task.WhenAny(task1, task2) ?
Not sure if I understand the question, however I'll take a stab at it.
Assuming you have "work" to do (that would normally run synchronously) but would like to wrap that in an async method, it would look sort of like the following:
public Task<String> DelayTimerCallAsync(CancellationToken cancellationToken)
{
return Task.Run(() => {
String messages = null;
/* do work here (assign messages a value) */
return messages;
}, cancellationToken);
}
If you're looking to do the opposite (run a Task synchronously) you can do so easily as well. To help, here's a simple class that makes things a bit easier:
public static class AsyncHelper
{
private static readonly TaskFactory taskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
public static void RunSync(Func<Task> func)
{
taskFactory
.StartNew<Task>(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return taskFactory
.StartNew<Task<TResult>>(func)
.Unwrap<TResult>()
.GetAwaiter()
.GetResult();
}
}
You then have the ability to execute Task methods without await. Something like:
// (referencing DelayTimeCallAsync above:
String messages = AsynHelper.RunSync(DelayTimeCallAsync(CancellationToken.None));

Resources