AspNetCore Integration Testing Multiple WebApplicationFactory Instances? - c#

Does any one know if it is possible to host multiple instances of WebApplicationFactory<TStartop>() in the same unit test?
I have tried and can't seem to get anywhere with this one issue.
i.e
_client = WebHost<Startup>.GetFactory().CreateClient();
var baseUri = PathString.FromUriComponent(_client.BaseAddress);
_url = baseUri.Value;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", "Y2E890F4-E9AE-468D-8294-6164C59B099Y");
WebHost is just a helper class that allows me to build factory and then a client easily in one line.
Under the covers all it does is this:
new WebApplicationFactory<TStartup>() but a few other things too.
It would be nice if i could stand up another instace of a different web server to test server to server functionality.
Does anyone know if this is possible or not?

Contrary to what the accepted answer states, it is actually pretty easy to test server to server functionality using two WebApplicationFactory instances:
public class OrderAPIFactory : WebApplicationFactory<Order>
{
public OrderAPIFactory() { ... }
protected override void ConfigureWebHost(IWebHostBuilder builder) { ... }
}
public class BasketAPIFactory : WebApplicationFactory<BasketStartup>
{
public BasketAPIFactory() { ... }
protected override void ConfigureWebHost(IWebHostBuilder builder) { ... }
}
Then you can instantiate the custom factories as follows:
[Fact]
public async Task TestName()
{
var orderFactory = new OrderAPIFactory();
var basketFactory = new BasketAPIFactory();
var orderHttpClient = orderFactory.CreateClient();
var basketHttpClient = basketFactory.CreateClient();
// you can hit eg an endpoint on either side that triggers server-to-server communication
var orderResponse = await orderHttpClient.GetAsync("api/orders");
var basketResponse = await basketHttpClient.GetAsync("api/basket");
}
I also disagree with the accepted answer about it necessarily being bad design: it has its use-cases. My company has a microservices infrastructure which relies on data duplication across microservices and uses an async messaging queue with integration events to ensure data consistency. Needless to say that messaging functionality plays a central role and needs to be tested properly. A test setup as described here is pretty useful in this situation. For example it allows us to thoroughly test how messages are being dealt with by a service that was down at the moment those messages were published:
[Fact]
public async Task DataConsistencyEvents_DependentServiceIsDown_SynchronisesDataWhenUp()
{
var orderFactory = new OrderAPIFactory();
var orderHttpClient = orderFactory.CreateClient();
// a new order is created which leads to a data consistency event being published,
// which is to be consumed by the BasketAPI service
var order = new Order { ... };
await orderHttpClient.PostAsync("api/orders", order);
// we only instantiate the BasketAPI service after the creation of the order
// to mimic downtime. If all goes well, it will still receive the
// message that was delivered to its queue and data consistency is preserved
var basketFactory = new BasketAPIFactory();
var basketHttpClient = orderFactory.CreateClient();
// get the basket with all ordered items included from BasketAPI
var basketResponse = await basketHttpClient.GetAsync("api/baskets?include=orders");
// check if the new order is contained in the payload of BasketAPI
AssertContainsNewOrder(basketResponse, order);
}

It is possible to host multiple communicating instances of WebApplicationFactory in single integration test.
Let's say we have master service named WebApplication, which depends on utility service named WebService using named HttpClient with name "WebService".
Here is example of integration test:
[Fact]
public async Task GetWeatherForecast_ShouldReturnSuccessResult()
{
// Create application factories for master and utility services and corresponding HTTP clients
var webApplicationFactory = new CustomWebApplicationFactory();
var webApplicationClient = webApplicationFactory.CreateClient();
var webServiceFactory = new WebApplicationFactory<Startup>();
var webServiceClient = webServiceFactory.CreateClient();
// Mock dependency on utility service by replacing named HTTP client
webApplicationFactory.AddHttpClient(clientName: "WebService", webServiceClient);
// Perform test request
var response = await webApplicationClient.GetAsync("weatherForecast");
// Assert the result
response.EnsureSuccessStatusCode();
var forecast = await response.Content.ReadAsAsync<IEnumerable<WeatherForecast>>();
Assert.Equal(10, forecast.Count());
}
This code requires CustomWebApplicationFactory class to be implemented:
// Extends WebApplicationFactory allowing to replace named HTTP clients
internal sealed class CustomWebApplicationFactory
: WebApplicationFactory<WebApplication.Startup>
{
// Contains replaced named HTTP clients
private ConcurrentDictionary<string, HttpClient> HttpClients { get; } =
new ConcurrentDictionary<string, HttpClient>();
// Add replaced named HTTP client
public void AddHttpClient(string clientName, HttpClient client)
{
if (!HttpClients.TryAdd(clientName, client))
{
throw new InvalidOperationException(
$"HttpClient with name {clientName} is already added");
}
}
// Replaces implementation of standard IHttpClientFactory interface with
// custom one providing replaced HTTP clients from HttpClients dictionary
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
services.AddSingleton<IHttpClientFactory>(
new CustomHttpClientFactory(HttpClients)));
}
}
And finally, CustomHttpClientFactory class is required:
// Implements IHttpClientFactory by providing named HTTP clients
// directly from specified dictionary
internal class CustomHttpClientFactory : IHttpClientFactory
{
// Takes dictionary storing named HTTP clients in constructor
public CustomHttpClientFactory(
IReadOnlyDictionary<string, HttpClient> httpClients)
{
HttpClients = httpClients;
}
private IReadOnlyDictionary<string, HttpClient> HttpClients { get; }
// Provides named HTTP client from dictionary
public HttpClient CreateClient(string name) =>
HttpClients.GetValueOrDefault(name)
?? throw new InvalidOperationException(
$"HTTP client is not found for client with name {name}");
}
The complete code of example you may find here: https://github.com/GennadyGS/AspNetCoreIntegrationTesting
The pros of such approach are:
ability to test interactions between the services;
no need to mock internals of services so that you can consider them as black boxes;
tests are stable to any refactorings including changes in communication protocol;
tests are fast, self-contained, do not require any prerequisites and give predictable results.
The main cons of such approach is possible conflicting dependencies of participating services (e.g. different major versions of EFCore) in real world scenarios due to the fact that all services using in test are running in single process.
There are several mitigations of such problem. One of them is to apply modular approach to services' implementations and load modules in runtime according to configuration file. This may allow to replace configuration file in tests, exclude several modules from loading and replace missing services with simpler mocks. The example of applying such approach you may find in branch "Modular" of the example repository above.

I was based on Gennadii Saltyshchak's solution to create this, which is exaclty what I was looking for: Two servers communicating with one another via a fallback mechanism.
In this example one server runs on port 80 and the other on 82 and there is an api endpoint called fallback that calls the hello endpoint on the fallback server.
Full solution can be found here: https://github.com/diogonborges/integration-test-communicating-servers
public class Tests
{
private HttpClient _port80Client;
private HttpClient _port82Client;
[SetUp]
public void Setup()
{
// Create application factories for master and utility services and corresponding HTTP clients
var port80Factory = new CustomWebApplicationFactory(80, 82);
_port80Client = port80Factory.CreateClient();
port80Factory.Server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature {Addresses = {"http://localhost:80"}});
var port82Factory = new CustomWebApplicationFactory(82, 80);
_port82Client = port82Factory.CreateClient();
port82Factory.Server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature {Addresses = {"http://localhost:82"}});
// Mock dependency on utility service by replacing named HTTP client
port80Factory.AddHttpClient(Constants.Fallback, _port82Client);
port82Factory.AddHttpClient(Constants.Fallback, _port80Client);
}
[Test]
public async Task Port80_says_hello()
{
var response = await _port80Client.GetAsync("hello");
var content = await response.Content.ReadAsStringAsync();
Assert.AreEqual("hello from http://localhost:80", content);
}
[Test]
public async Task Port80_falls_back_to_82()
{
var response = await _port80Client.GetAsync("hello/fallback");
var content = await response.Content.ReadAsStringAsync();
Assert.AreEqual("hello from http://localhost:82", content);
}
[Test]
public async Task Port82_says_hello()
{
var response = await _port82Client.GetAsync("hello");
var content = await response.Content.ReadAsStringAsync();
Assert.AreEqual("hello from http://localhost:82", content);
}
[Test]
public async Task Port82_falls_back_to_80()
{
var response = await _port82Client.GetAsync("hello/fallback");
var content = await response.Content.ReadAsStringAsync();
Assert.AreEqual("hello from http://localhost:80", content);
}
}

No. It's not possible. WebApplicationFactory leans on xUnit's IClassFixture, which has to be applied at the class level, meaning you only get one bite at the apple. The WebApplicationFactory itself is capable of being customized per test, which fulfills most use cases where you're need a "different" one, but it doesn't help you wanting two totally separate active test servers at the same time.
However, that said, what you're wanting is a bad test design in the first place. The whole point of testing is to eliminate variables so you can actually ensure the piece of the SUT is actually working. Even in an integration testing environment, you're still just looking at one particular interaction between pieces of your application. Have two test servers, feeding off each other, effectively multiplies the variables giving you no assurance that either side is working correctly.

Related

How to do integration testing on an external API with ASP.NET Core

I'm trying to do some integration tests on an external API. Most of the guides I find online are about testing the ASP.NET web api, but there's not much to find about external API's. I want to test a GET request on this API and confirm if it passes by checking if the status code is OK. However this test is not passing and im wondering if i'm doing this correctly. Currently it's giving me a status code 404(Not found).
I'm using xUnit together with Microsoft.AspNetCore.TestHost How would you suggest me to test external API's?
private readonly HttpClient _client;
public DevicesApiTests()
{
var server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseStartup<Startup>());
_client = server.CreateClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method)
{
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Edit
The API call which im trying to test looks as follows, and is working properly
private readonly DbContext _dbContext;
private readonly IDevicesRepository _devicesRepository;
public DevicesAPIController(DbContext dbContext, IDevicesRepository devicesRepository)
{
_dbContext = dbContext;
_devicesRepository = devicesRepository;
}
[HttpPost("PostLiveDevicesToDatabase")]
public async Task<IActionResult> PostLiveDevicesToDatabase()
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
using (var response = await httpClient
.GetAsync(
"https://prtg.nl/api/content=Group,Device,Status")
)
{
string apiResponse = await response.Content.ReadAsStringAsync();
var dataDeserialized = JsonConvert.DeserializeObject<Devices>(apiResponse);
devicesList.AddRange(dataDeserialized.devices);
foreach (DevicesData device in devicesList)
{
_dbContext.Devices.Add(device);
devicesAdded.Add(device);
_dbContext.SaveChanges();
}
}
}
}
catch
{
return BadRequest();
}
}
I would like to propose an alternative solution which involves changing the design of the code to be tested.
The currently shown test-case is coupled to the external API and tests its ability to respond 200 OK rather than your code (i.e., your code isn't referenced at all). This also means that if a connection can't be established to the server (e.g., could be an isolated build agent in a CI/CD pipeline or just a flaky café WIFI) the test fails for another reason than what is asserted.
I would propose to extract the HttpClient, and its configuration that is specific to the API, into an abstraction as you have done with the IDevicesRepository (although it's not used in the example). This allows you to substitute the response from the API and only test your code. The substitutions could explore edge-cases such as the connection down, empty response, malformed response, external server error etc. That way you can exercise more failure-paths in your code and keep the test decoupled from the external API.
The actual substitution of the abstraction would be done in the "arrange" phase of the test. You can use the Moq NuGet package for this.
Update
To provide an example of using Moq to simulate an empty API response consider a hypothetical abstraction such as:
public interface IDeviceLoader
{
public IEnumerable<DeviceDto> Get();
}
public class DeviceDto
{
// Properties here...
}
Keep in mind the example abstraction isn't asynchronous, which could be considered best practices as you are invoking I/O (i.e., the network). I skipped it to keep it simple. See Moq documentation on how to handle async methods.
To mock the response the body of the test case could be:
[Fact]
public async Task CheckEndpointHandlesEmptyApiResponse()
{
// How you get access to the database context and device repository is up to you.
var dbContext = ...
var deviceRepository = ...
//Arrange
var apiMock = new Mock<IDeviceLoader>();
apiMock.Setup(loader => loader.Get()).Returns(Enumerable.Empty<DeviceDto>());
var controller = new DevicesAPIController(dbContext, deviceRepository, apiMock.Object);
//Act
var actionResponse = controller.PostLiveDevicesToDatabase();
// Assert
// Check the expected HTTP result here...
}
Do check the Moq documentation on their repository (linked above) for more examples.
The base address of test server is localhost. TestServer is meant for in-memory integration tests. The client created via TestServer.CreateClient() will create an instance of HttpClient that uses an internal message handler to manage requests specific you your API.
If you are trying to access an external URL by calling the test server. You will get 404 by design.
If https://prtg.nl/api/content is not local to your API and is the actual external link you want to access then use an independent HttpClient
//...
private static readonly HttpClient _client;
static DevicesApiTests() {
_client = new HttpClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method) {
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
//...
If this is meant to be an end to end via your api then you need to call the local API end point which is dependent on the target controller and action
The example in accepted solution is not an integration test, it's unit test. While it's usable in simple scenarios, I wouldn't recommend you to test controllers directly. On integration test level, controller is an implementation detail of your application. Testing implementation details is considered a bad practice. It makes your tests more flaky and less maintainable.
Instead, you should test your API directly using WebApplicationFactory from Microsoft.AspNetCore.Mvc.Testing package.
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests
Here is how I would do it
Implementation
Add typed client wrapper around HttpClient
public class DeviceItemDto
{
// some fields
}
public interface IDevicesClient
{
Task<DeviceItemDto[]?> GetDevicesAsync(CancellationToken cancellationToken);
}
public class DevicesClient : IDevicesClient
{
private readonly HttpClient _client;
public DevicesClient(HttpClient client)
{
_client = client;
}
public Task<DeviceItemDto[]?> GetDevicesAsync(CancellationToken cancellationToken)
{
return _client.GetFromJsonAsync<DeviceItemDto[]>("/api/content=Group,Device,Status", cancellationToken);
}
}
Register your typed client in DI
public static class DependencyInjectionExtensions
{
public static IHttpClientBuilder AddDevicesClient(this IServiceCollection services)
{
return services.AddHttpClient<IDevicesClient, DevicesClient>(client =>
{
client.BaseAddress = new Uri("https://prtg.nl");
});
}
}
// Use it in Startup.cs
services.AddDevicesClient();
Use typed client in your controller
private readonly IDevicesClient _devicesClient;
public DevicesController(IDevicesClient devicesClient)
{
_devicesClient = devicesClient;
}
[HttpGet("save")]
public async Task<IActionResult> PostLiveDevicesToDatabase(CancellationToken cancellationToken)
{
var devices = await _devicesClient.GetDevicesAsync(cancellationToken);
// save to database code
// you can return saved devices, or their ids
return Ok(devices);
}
Tests
Add fake HttpMessageHandler for mocking HTTP responses
public class FakeHttpMessageHandler : HttpMessageHandler
{
private HttpStatusCode _statusCode = HttpStatusCode.NotFound;
private HttpContent? _responseContent;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(_statusCode)
{
Content = _responseContent
};
return Task.FromResult(response);
}
public FakeHttpMessageHandler WithDevicesResponse(IEnumerable<DeviceItemDto> devices)
{
_statusCode = HttpStatusCode.OK;
_responseContent = new StringContent(JsonSerializer.Serialize(devices));
return this;
}
}
Add custom WebApplicationFactory
internal class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Use the same method as in implementation
services.AddDevicesClient()
// Replaces the default handler with mocked one to avoid calling real API in tests
.ConfigurePrimaryHttpMessageHandler(() => new FakeHttpMessageHandler());
});
}
// Use this method in your tests to setup specific responses
public WebApplicationFactory<Program> UseFakeDevicesClient(
Func<FakeHttpMessageHandler, FakeHttpMessageHandler> configureHandler)
{
var handler = configureHandler.Invoke(new FakeHttpMessageHandler());
return WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddDevicesClient().ConfigurePrimaryHttpMessageHandler(() => handler);
});
});
}
}
Test will look like this:
public class GetDevicesTests
{
private readonly CustomWebApplicationFactory _factory = new();
[Fact]
public async void Saves_all_devices_from_external_resource()
{
var devicesFromExternalResource => new[]
{
// setup some test data
}
var client = _factory
.UseFakeDevicesClient(_ => _.WithDevicesResponse(devicesFromExternalResource))
.CreateClient();
var response = await client.PostAsync("/devices/save", CancellationToken.None);
var devices = await response.Content.ReadFromJsonAsync<DeviceItemDto[]>();
response.StatusCode.Should().Be(200);
devices.Should().BeEquivalentTo(devicesFromExternalResource);
}
}
Code example
You can customise CustomWebApplicationFactory and FakeHttpMessageHandler according to your test cases, but I hope the idea is clear

Unit testing a controller action which calls a private method that uses HTTPClient

I am a newbie to C# and TDD. I am developing a product in which I need to write unit tests for some HTTP API calls. Below is how a controller looks like:
public class CommunicationController : ControllerBase
{
private readonly IHttpClientFactory _clientFactory;
private readonly AppSettings _appSettings;
public CommunicationController(IHttpClientFactory clientFactory, IOptions<AppSettings> appSettings)
{
_clientFactory = clientFactory;
_appSettings = appSettings.Value;
}
[HttpPost]
public async Task<IActionResult> PostEntity([FromBody] Entity entity)
{
if (entity.foo == null)
{
NoActionsMessage noActionsMessage = new NoActionsMessage
{
Message = "No actions performed"
};
return Ok(noActionsMessage);
}
var accessTokenDatails = await GetAccessTokenDetailsAsync();
var callUrl = "http://someUrlGoesHere";
var json = JsonConvert.SerializeObject(entity);
var content = new System.Net.Http.StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Put, new Uri(callUrl))
{
Content = content
};
request.Headers.Add("accessToken", accessTokenDatails.AccessToken);
return await InvokeHttpCall(request);
}
private async Task<AccessTokenDetails> GetAccessTokenDetailsAsync()
{
var appId = _appSettings.AppId;
var appSecret = _appSettings.AppSecret;
var refreshToken = _appSettings.RefreshToken;
var request = new HttpRequestMessage(HttpMethod.Get, new Uri("sometokenproviderUrl"));
request.Headers.Add("applicationId", appId);
request.Headers.Add("applicationSecret", appSecret);
request.Headers.Add("refreshToken", refreshToken);
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseStream = response.Content.ReadAsStringAsync();
// [ALERT] the failing line in unit test - because responseStream.Result is just a GUID and this the the problem
var result = JsonConvert.DeserializeObject<AccessTokenDetails>(responseStream.Result);
return result;
}
else
{
throw new ArgumentException("Unable to get Access Token");
}
}
}
This POST method which is calling a private method. By calling this post method with appropriate entity given:
1. Should make a call to the token provider service and get the token
2. Using the token, authenticate the service to add the entity
AccessTokenDetails class looks is below:
public sealed class AccessTokenDetails
{
[JsonProperty("accessToken")]
public string AccessToken { get; set; }
[JsonProperty("endpointUrl")]
public Uri EndpointUrl { get; set; }
[JsonProperty("accessTokenExpiry")]
public long AccessTokenExpiry { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
}
Now when it comes to unit testing (I am using XUnit) I have a test method like below:
public async Task Entity_Post_Should_Return_OK()
{
/ Arrange - IHttpClientFactoryHttpClientFactory
var httpClientFactory = new Mock<IHttpClientFactory>();
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
var fixture = new Fixture();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(fixture.Create<string>),
});
var client = new HttpClient(mockHttpMessageHandler.Object);
client.BaseAddress = fixture.Create<Uri>();
httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
// Arrange - IOptions
var optionsMock = new Mock<IOptions<AppSettings>>();
optionsMock.SetupGet(o => o.Value).Returns(new AppSettings
{
AppId = "mockappid",
AppSecret = "mockappsecret",
RefreshToken = "mockrefreshtoken"
});
// Arrange - Entity
AddActionEntity entity = new Entity();
entity.foo = "justfoo";
// Act
var controller = new CommunicationController(httpClientFactory.Object, optionsMock.Object);
var result = await controller.PostEntity(entity);
// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom<OkObjectResult>(result);
}
This particular test case is failing in the when calling the PostEntity method as it failed to deserialize the responseStream.Result in the GetAccessTokenDetailsAsync() private method, to AccessTokenDetails in this unit test. The deserialization failed as the value of responseStream.Result is just a GUID string.
Can anyone please tell me that I am getting into a "dependency inversion" problem and tell me a way to overcome this?
I am thinking of separating the GetAccessTokenDetailsAsync to a different class, something like AccessTokenProvider and mock it to over come it - will it be a good approach? what could be a best approach to solve this problem.
ok,let's get a few things straight.
not everything should be unit tested. You have an API and you have a dependency on a token service. Those 2 things need to be integration tested. Mocking and calling API methods won't give you any value.
Unit test business functionality. The moment you start talking about mocking controllers you're going down on a path that serves no real purpose. You need to decouple your business functionality from your controllers
You're not doing TDD. TDD means you're starting with failing tests, the first thing you do is write tests, then start to write code to satisfy those tests. If you had done that from beginning all these issues you uncover now would have been solved already.
Learn how to properly call an API. You mention using responseStream.Result . That's the sign of someone who doesn't know how to use async properly. You need to await your calls properly.
Here's an example based on a quick search : How do I correctly use HttpClient with async/await?
NB. Http client is not supposed to be used inside a using block, that's actually counter productive. Go over this, for example: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
if you want to do proper unit testing, then stop thinking in terms of controllers and start thinking in terms of functionality. You do not need to mock a controller if your code is properly separated. You can simply unit tests those separate classes / libraries outside of your API.
if you want the certainty that your API actually works, stop mocking calls. Make real calls to it, plan your inputs and check the outputs. That's why I said that you integration test endpoints.
Same applies to the token endpoints. Use real calls, get real tokens and see what happens when things go wrong.

Mock service for xUnit

I have an application that currently works as designed, but I am trying to setup integration testing with xUnit before I expand upon it. At the moment the test will only use the original service when performing the test and I don't see why.
This the is the test:
using IStoreRepository = Repositories.V3.Interfaces.IStoreRepository;
public class StoreTests : IClassFixture<WebApplicationFactory<Startup>> {
private readonly ITestOutputHelper _output;
private readonly WebApplicationFactory<Startup> _factory;
private readonly string _url;
public StoreTests(ITestOutputHelper output, WebApplicationFactory<Startup> factory) {
_output = output;
_factory = factory;
_url = "/api/store";
}
[Theory]
[InlineData("GET", "FAKE123")]
public async Task StoreByCode(string method, string code = null) {
// var client = _factory.CreateClient();
var client = _factory.WithWebHostBuilder(builder => {
builder.ConfigureTestServices(services => {
services.AddScoped<IStoreRepository, StoreRepositoryTest>();
});
}).CreateClient();
var request = new HttpRequestMessage(new HttpMethod(method), $"{_url}/{code}");
string readAsStringAsync;
_output.WriteLine($"Request Uri: {request.RequestUri}");
using (var response = await client.SendAsync(request)) {
response.EnsureSuccessStatusCode();
readAsStringAsync = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) {
_output.WriteLine($"Not successful ({response.StatusCode}): {readAsStringAsync}");
}
}
var stores = JsonConvert.DeserializeObject<List<Store>>(readAsStringAsync);
Assert.True(stores.Any());
}
}
However when I conduct the test the break point in the real Repository, StoreRepository that is registered in Startup.cs is the one that is hit, not the break point in StoreRepositoryTest. I setup my factory to override the dependency, but it's ignoring it. What can I do to correct this.
For reference, I have been using this source: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2
Update
Not sure if this question should be deleted or not but it ended up being a really silly mistake. I updated the code above to include a using alias. It turned out I was registering and overriding the V1 interface instead of V3. When I implemented the Mock class I didn't realize I was implementing the old service. So the good news is the above code is a working example of how to mock using Microsoft's documentation.
I have seen this before. You probably created an interface in a different namespace.
Typically, this happens when you have a version 1 interface for a web service, and then you decide to add new functionality. You then create a version 2 interface with exactly the same name.
Put a break point on services.AddScoped<IStoreRepository, StoreRepositoryTest>() and debug that. Inspect the results and scroll to the bottom where your services are being added; You’ll get a clear view of what’s being added.
1) You can try using CustomWebApplicationFactory class and in ConfigureWebHost(IWebHostBuilder builder) method, add builder.ConfigureServices(//define dependencies).
Refer msdn link
2) Alternatively, you can define your own TestStartup class inheriting from Startup class and use it.

Design pattern to consume REST API

I am making a DLL to consume a REST API in aspnetcore.
Ideally, I would like it to be accessed this way:
API api = new API(clientInfo);
api.Module.Entity.Action(params);
But I am struggling to make that a reality. I can't make anything static because more than 1 session might be instanced at the same time. I can't pass the session around except by reference otherwise session state(cookies etc.) might change in the copy. Is there a design pattern I should be using?
public class API
{
private Session _session;
public API(ClientInfo clientInfo)
{
_session = new Session(clientInfo);
}
}
The session serves as middleware for the client, stores login data in case the client needs to repeat login, handles some errors/retries and exposes client methods.
public class Session
{
private Client _client;
private string _path;
public Session(ClientInfo clientInfo)
{
_client= new Client(clientInfo);
_path = clientInfo.Path;
}
public HttpResponseMessage Get(string name, string arguments = "")
{
return _client.Get(_path, name, arguments);
}
...
}
The client actually performs the calls.
public class Client
{
public HttpResponseMessage Get(string path, string endpointName, string arguments)
{
return GetClient().GetAsync(path + endpointName + arguments).Result;
}
private HttpClient GetClient(){...}
...
}
Personally, I just build a simple client for my APIs, with methods corresponding to the endpoints the API has:
public class FooClient
{
private readonly HttpClient _httpClient;
public FooClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<GetFooResult> Get(int id)
{
...
}
// etc
}
The HttpClient dependency is provided by registering a typed client in Startup.cs:
services.AddHttpClient<FooClient>(c =>
{
// configure client
});
And I add an IServiceCollection extension to encapsulate this and any other setup logic:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
{
...
}
}
Then, in my Startup, I can simply do something like:
services.AddFooClient(Configuration.GetValue<string>("FooUri"));
This is extremely helpful for setting up automatic error handling, retry policies, etc. via Polly, as you can then set up all that configuration just once in the extension.
Now, getting to your issue of persisting things like auth tokens, you have a few possibilities. I tend to prefer persisting auth tokens as claims, in which case you can simply retrieve the claim and pass it into methods on your client that need it:
var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));
If you handle things that way, you can bind your client in any scope, including singleton.
An alternative approach would be to actually persist the auth token in your client, but this has to be done with care. You should definitely avoid using singleton scope, unless you're employing something like a ConcurrentDictionary and even then, ensuring that the right token is always used could be a bit gnarly.
Assuming you're using a request scope, you can store the token directly as an ivar or something, but you'd still need to persist it some place else beyond that, or you'd still need to re-auth for each request. If you were to store it in the session, for example, then you could do something like:
services.AddScoped<FooClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();
var httpClient = httpClientFactory.Create("ClientName");
var session = httpContextAccessor.HttpContext.Session;
var client = new FooClient(httpClient);
client.SetAuthToken(session["FooAuthToken"]);
});
However, even then, I'd still say it's better to pass the auth token into the method than do any of this. It's more explicit about which actions require auth versus those that do not, and you always know exactly what's coming from where.
One of your biggest problems will be the reuse of the HttpClient. This is a known problem for "pre-Core" days. Luckily, its been addressed and as of Net Core 2.1 we now have an HttpClientFactory which allows you to spin up as manage HttpClients as you need and they're handled for you as part of the framework.
https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore
With this in mind, theres nothing stopping you from using DI to inject an IHttpClientFactory which will provide you with the necessary access to the pipeline you need. Other than that, its entirely up to you how you design the code which "manages" your access to the REST resources. Maybe some sort of Repository Pattern? (Purely guess work really without knowing your architecture etc)

Async provider in .NET Core DI

I'm just wondering if it's possible to have async/await during DI.
Doing the following, the DI fails to resolve my service.
services.AddScoped(async provider =>
{
var client = new MyClient();
await client.ConnectAsync();
return client;
});
where as the following works perfectly fine.
services.AddScoped(provider =>
{
var client = new MyClient();
client.ConnectAsync().Wait();
return client;
});
Although it is theoretically possible to use async/await during object resolution, you should consider the following constraints:
Constructors can't be asynchronous, and
Construction of object graphs should be simple, reliable and fast
Because of these constraints, verything that involves I/O should be postponed until after the object graph has been constructed.
So instead of injecting a connected MyClient, MyClient should connect when it is used for the first time—not when it is created.
Since your MyClient is not an application component but a third-party component, this means that you can't ensure that it "connect[s] when it is used for the first time."
This shouldn't be a problem, however, because the Dependency Inversion Principle already teaches us that:
the abstracts are owned by the upper/policy layers
This means that application components should not depend on third-party components directly, but instead they should depend on abstractions defined by the application itself. As part of the Composition Root, adapters can be written that implement these abstractions and adapt application code to the third-party libraries.
An important advantage of this is that you are in control over the API that your application components use, which is the key to success here, as it allows the connectivity issues to be hidden behind the abstraction completely.
Here's an example of how your application-tailored abstraction might look like:
public interface IMyAppService
{
Task<Data> GetData();
Task SendData(Data data);
}
Do note that this abstraction lacks an ConnectAsync method; this is hidden behind the abstraction. Take a look at the following adapter for instance:
public sealed class MyClientAdapter : IMyAppService, IDisposable
{
private readonly Lazy<Task<MyClient>> connectedClient;
public MyClientAdapter()
{
this.connectedClient = new Lazy<Task<MyClient>>(async () =>
{
var client = new MyClient();
await client.ConnectAsync();
return client;
});
}
public async Task<Data> GetData()
{
var client = await this.connectedClient.Value;
return await client.GetData();
}
public async Task SendData(Data data)
{
var client = await this.connectedClient.Value;
await client.SendData(data);
}
public void Dispose()
{
if (this.connectedClient.IsValueCreated)
{
this.connectedClient.Value.Dispose();
}
}
}
The adapter hides the connectivity details from the application code. It wraps the creation and connection of MyClient in a Lazy<T>, which allows the client to be connected just once, independently of in which order the GetData and SendData methods are called, and how many times.
This allows you to let your application components depend on IMyAppService instead of MyClient and register the MyClientAdapter as IMyAppService with the appropriate lifestyle.

Categories

Resources