Unit Tests and incapsulation - c#

For example, I have a class, working with HttpClient
public class DomainActions : IDomainActions
{
private readonly HttpClient _client;
private readonly IConfiguration _configuration;
public DomainActions(IConfiguration configuration)
{
_configuration = configuration;
_client = new HttpClient()
{
BaseAddress = new Uri(_configuration.GetSection("DomainRegistration:BaseAddress").Value)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.GetSection("DomainRegistration:Token").Value);
}
public async Task<List<DomainDto>> GetDomainListAsync()
{
var responseMessage = await _client.GetAsync("domains");
return await ProcessingDomainListResponseAsync(responseMessage);
}
then we resolve it by the following way:
services.AddTransient<IConfiguration>(....);
services.AddTransient<IDomainActions, DomainActions>();
and client class:
public class AddMxRecordToRegistrator
{
protected readonly IDomainActions domainActions;
public AddMxRecordToRegistrator(IDomainActions domainActions )
{
this.domainActions = domainActions ;
}
public async Task CreateDomainRecordAsync()
{
await domainActions.CreateDomainRecordAsync(queueItem.DomainForRegistration.DomainName, new DomainRegistrationCore.Models.DomainRecordDto
{
Content = queueItem.MxRecord,
Name = String.Empty,
Priority = 0,
Ttl = 3600,
Type = DomainRecordType.MX.ToString(),
Regions = null
});
ok, it works fine.
Right now, I want to create unit test for AddMxRecordToRegistrator class , but I don't want to use real httpClient. How to do it? Of course, I can add one more dependency:
public class DomainActions : IDomainActions
{
private readonly HttpClient _client;
private readonly IConfiguration _configuration;
public DomainActions(IConfiguration configuration, HttpMessageHandler httpMessageHandler)
{
_configuration = configuration;
_client = new HttpClient(httpMessageHandler)
{
BaseAddress = new Uri(_configuration.GetSection("DomainRegistration:BaseAddress").Value)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.GetSection("DomainRegistration:Token").Value);
}
public DomainActions(IConfiguration configuration) : this(configuration, new HttpClientHandler())
{
}
public async Task<List<DomainDto>> GetDomainListAsync()
{
var responseMessage = await _client.GetAsync("domains");
return await ProcessingDomainListResponseAsync(responseMessage);
}
then modify DI composition root:
services.AddTransient<IConfiguration>(....);
services.AddTransient<HttpMessageHandler>(....);
services.AddTransient<IDomainActions, DomainActions>();
but then why client part (in our case composition root) should know anything about internal detail of DomainActions only because we need to create unit test? It like we violate incapsulation for unit tests. How to implement it correctly?

To expand on the comment from #CamiloTerevinto, AddMxRecordToRegistrator should depend on IDomainActions via dependency injection, i.e. that interface should be the argument passed to its constructor.
From an encapsulation perspective, AddMxRecordToRegistrator shouldn't know that DomainActions depends on IConfiguration or HttpMessageHandler. It shouldn't even know that DomainActions exists, because that's a concrete class, and AddMxRecordToRegistrator should depend on interfaces, not concrete classes.

but then why client part (in our case composition root) should know
anything about internal detail of DomainActions only because we need
to create unit test?
Composition root is only place in application which will "know" about all lower level dependencies.
"Composition" root's role is to compose required classes with runtime implementations.
Class AddMxRecordToRegistrator clearly depends on abstraction IDomainActions, so for unit testing AddMxRecordToRegistrator you just pass fake implementation of IDomainActions.

Related

How to test interface which has dependency injection with option pattern?

Let's say I have a web api application which has this service interface.
public interface IMyService
{
Task<int> Process(string processType);
}
and this is my service class. I am using option pattern.
public class MyService : IMyService
{
private readonly MyOptions _myOptions;
private readonly MyContext _myContext;
private readonly IHttpClientFactory _httpClientFactory;
public MyService(IOptions<myOptions> myOptions,
MyContext myContext,
IHttpClientFactory httpClientFactory)
{
_myOptions = myOptions.Value;
_myContext = myContext;
_httpClientFactory = httpClientFactory;
}
}
and this is how I register it in Program.cs. I am using .NET 6 framework.
var builder = WebApplication.CreateBuilder(args);
var myConnStr =
builder.Configuration.GetConnectionString("MyConnection");
// EF Core
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(myConnStr);
});
builder.Services.AddOptions().Configure<MyOptions>(builder.Configuration.GetSection("ABC"));
builder.Services.AddHttpClient();
builder.Services.AddScoped<IMyService, MyService>();
// some code are removed for brevity
How do I test Process method in my service using Xunit? Note: Although the web api is calling this method, I do not want to test the web api. The reason is because this is actually a background process. Thus there won't be much thing return in the controller (web api). I also don't feel the need to mock it if possible to simplify the integration test.
I managed to do something like this.
public class MyServiceTest
{
private const string sqlServerConnection = "xxx";
private IMyService _myService;
private MyContext _myContext;
public MyServiceTest()
{
_configuration = new ConfigurationBuilder().AddJsonFile("appsettings.test.json").Build();
}
[Fact]
public async Task WhenProcessIsInValid_ThenReturn0()
{
// Arrange
SetupSqlServerContext();
await SetupMockData();
// _myService = new MyService(_myContext);
}
}
private void SetupSqlServerContext()
{
var options = new DbContextOptionsBuilder<MyContext>();
options.UseSqlServer(sqlServerConnection);
_myContext = new SqlServer.MyContext(options.Options);
}
I am stuck on creating the instance for MyService. How do I pass it IHttpClientFactory and IOptions<yOptions> to its constructor?
Use one of the mocking frameworks like Moq, for example, to mock the IHttpClientFactory.
It will look similar to this
var factoryMock = new Mock<IHttpClientFactory>();
factoryMock
.Setup(factory => factory.CreateClient(It.IsAny<string>()))
.Returns(httpClientMock.Object);
Check this answer to learn how to mock HttpClient.
For IOptions<MyOptions> it's even simpler, just create an instance of it using this code
var options = Options.Create(new MyOptions{ });
And finally, instantiate the MyService
_myService = new MyService(options, _myContext, factoryMock.Object);

Can I reuse Httpclient in Integration tests in ASP.NET Core?

I have an Asp.net core 6 Web api.
I make Integration tests with xUnit following the pattern here. WebApplicationFactory creates a client which the tests can use to make calls to the application instance.
Now, xUnit runs the class constructor for every test. To share a member between all tests in a test class, you need to use a xUnit class fixture. This is the example from MSDN:
public class IndexPageTests :
IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<RazorPagesProject.Startup>
_factory;
public IndexPageTests(
CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
I don't understand - why they put the CreateClient method in the constructor? Obviously, it will create a new client for every test. Why don't we create a separate Fixture class, create the client there and just inject it in the Test class?
For example:
public class TestFixture
{
public TestFixture()
{
var factory = new CustomWebApplicationFactory<RazorPagesProject.Startup>();
Client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
public HttpClient Client { get; }
}
public class TestClass : IClassFixture<TestFixture>
{
private readonly TestFixture _fixture;
public TestClass(TestFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task FirstTest()
{
// use the fixture client directly
await _fixture.Client.GetAsync("..../url");
}
}
Does anyone see a problem with this approach? Memory leaks or other issues?
Is the fact that we instantiate CustomWebApplicationFactory<RazorPagesProject.Startup>() in the fixture constructor and not disposing it afterwards - causing memory problems, etc?

How to implement IHttpFactoryClient in examples of Typed HttpClient?

In every example I've seen, including the Microsoft ones here and here, the author's explain the improvements made by IHttpClientFactory over HttpClient and give examples of how to use it simply out-of-the-box or in Named form. But then they all seem to mention that utilizing the Typed form really is best for its structure, usability, and more. The reasons make sense for our use case.
Though like the links provided above, there isn't a single line of code instantiating, injecting, or using IHttpClientFactory in the involvement of creating a Typed HttpClient (or Service as a Client). You create the Typed Client:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
and then you consume it in some model or controller:
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
I'm extremely confused. My team was originally hitting roadblocks trying to mock (with Moq) Typed clients for unit testing, and the conclusion that we came to after many great resources was that mocking was exponentially easier with IHttpClientFactory. But I haven't found a single example that explicitly uses IHttpClientFactory with Typed clients.
The framework will use ITypedHttpClientFactory to create the HttpClient to be injected into the typed client. This is happening under the hood when the typed client is configured like so:
services.AddHttpClient<ICatalogService, CatalogService>()
If we peek into AddHttpClient, we can see that it'll attempt to create a transient version of IHttpClientFactory called ITypedHttpClientFactory
services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>), typeof(DefaultTypedHttpClientFactory<>)));
Typed clients do also allow for abstracted clients
public class GitHubService :IGitHubService { // <-- NOTE THE INTERFACE
HttpClient client
public GitHubService(HttpClient client) {
this.client = client;
}
//...
Where the interface is registered along with its implementation using AddHttpClient
services.AddHttpClient<IGitHubService, GitHubService>();
and used accordingly
//...
private readonly IGitHubService gitHubService;
public TypedClientModel(IGitHubService gitHubService) {
this.gitHubService = gitHubService;
}
public async Task OnGet() {
try {
LatestIssues = await gitHubService.GetAspNetDocsIssues();
}
//...
The advantage here is that You decouple from 3rd party dependencies (framework concerns) since you are the one in control of the typed client and its abstraction.
This will allow for easier mocking of the typed client abstraction when testing in isolation.
With IHttpClientFactory you have three options:
IHttpClientFactory
Usage
public class SampleController
{
private readonly IHttpClientFactory _clientFactory;
public SampleController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
}
Mocking
//Arrange
var mockClientFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(expectedResponseMessage);
var client = new HttpClient(mockMessageHandler.Object);
mockClientFactory
.Setup(_ => _.CreateClient(It.IsAny<string>()))
.Returns(client);
Named client
Usage
public class SampleController
{
private readonly HttpClient _client;
public SampleController(IHttpClientFactory clientFactory)
{
_client = clientFactory.CreateClient("SampleProxy");
}
}
Mocking
As an alternative we can avoid the usage of Moq.Protected by using a custom HttpMessageHandler
public class FakeMessageHandler: HttpMessageHandler
{
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
}
//Arrange
var mockClientFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<FakeMessageHandler> { CallBase = true };
mockMessageHandler
.Setup(handler => handler.Send(It.IsAny<HttpRequestMessage>()))
.Returns(expectedResponseMessage);
var client = new HttpClient(mockMessageHandler.Object);
mockClientFactory
.Setup(_ => _.CreateClient("SampleProxy"))
.Returns(client);
Typed client
Usage
public class SampleController
{
private readonly ISampleClient _client;
public SampleController(ISampleClient client)
{
_client = client;
}
}
Mocking
//Arrange
var clientMock = new Mock<ISampleClient>();
clientMock
.Setup(client => client.GetXYZ(It.IsAny<SampleRequest>()))
.ReturnsAsync(expectedSampleResponse);
var SUT = new SampleController(clientMock.Object);

how to inject a httprequestmessage / endpoint

I have a controller in my REST API, where I do a HttpRequestMessage. The way I am doing it right now is by using the IConfiguration interface to fetch the endpoint as a variable:
public class MyController : Controller
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
public MyController(IConfiguration configuration, HttpClient httpClient){
_configuration = configuration;
_httpClient = httpClient;
}
...
...
[HttpGet]
public async Task<IActionResult> Get(){
...
...
var httpRequest = new HttpRequestMessage(HttpMethod.Get, _configuration["MY_ENDPOINT"]);
await _httpClient.SendAsync(httpRequest);
...
...
return Ok();
}
The thing is, that it is apparently better to have the api endpoint injected via an interface and I honestly don't know what or how that is done.
I do inject the HttpClient and the IConfiguration, but that's something I've done several times and seen others do. But just injecting an endpoint (without IConfiguration), seems unfamiliar for me. The ... is just because I've taken out code that doesn't have influence on the question.
Is there any simple way to just inject the endpoint - and is it just me that don't understand the reason for it?
I guess I have to create an interface and in that some logic that just returns the endpoint? But isn't that just double work?
MY SOLUTION:
the only workaround I can think of at the moment is just by injecting a string:
private readonly string _myEndpoint;
and then inject it:
_myEndpoint = Environment.GetEnvironmentVariable("MY_ENDPOINT");
and finally use that in my httpRequestMessage:
var httpRequest = new HttpRequestMessage(HttpMethod.Get, _myEndpoint);
That's not an interface, but again I don't use the IConfiguration-interface and don't write a lot of unneeded code.
If any better / smarter suggestion, then please shout out.
There is a way that you can load "options" into the service collection via:
services.Configure<EndpointConfig>(Configuration.GetSection("EndPointConfig"));
The EndpointConfig here is a class that you would have to define:
public class EndpointConfig
{
public string EndpointUrl {get;set;}
}
In this particular example the appsettings.json "EndPointConfig" would need a EndpointUrl, here's a rough example:
{
"EndPointConfig" : {
"EndpointUrl" : "https://localhost"
}
}
then when you get to your controller you pass in the config like so:
private readonly EndpointConfig _configuration;
private readonly HttpClient _httpClient;
public MyController(IOptions<EndpointConfig> configuration, HttpClient httpClient){
_configuration = configuration.Value;
_httpClient = httpClient;
}
There is some good documentation around this if you would like to try it: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1
If you don't want to define a EndpointConfig section in your appsettings.json as your describing in the comments then you would simply configue i with the Configuration object:
services.Configure<EndpointConfig>(Configuration);
Now it will search for the property name (in this case EndpointUrl) in the base object of your appsettings json:
{
"EndpointUrl" : "https://localhost"
}
If you want to look for a different name i.e. My_Endpoint you would have to simply rename your property:
public class EndpointConfig
{
public string My_Endpoint {get; set;}
}

Creating stub for `private static readonly` field

Due on Improper Instantiation problem it is recommended to create private static readonly instance of HttpClient.
Due on lack of time I have injected mocked client into test method with client as their parameter.
The problem is how can I in simple way inject mock into private static readonly HttpClient field of SingleHttpClientInstanceController?
how can I in simple way inject mock into private static readonly
HttpClient field of SingleHttpClientInstanceController?
Answer: There is no simple way.
Suggestion:
Abstract the resource behind an accessor
public interface IHttpClientAccessor {
HttpClient HttpClient { get; }
}
and inject that into the dependent controller.
public class SingleHttpClientInstanceController : ApiController {
private readonly HttpClient HttpClient;
public SingleHttpClientInstanceController(IHttpClientAccessor httpClientAccessor) {
HttpClient = httpClientAccessor.HttpClient;
}
// This method uses the shared instance of HttpClient for every call to GetProductAsync.
public async Task<Product> GetProductAsync(string id) {
var hostName = HttpContext.Current.Request.Url.Host;
var result = await HttpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
return new Product { Name = result };
}
}
The same should also be done for accessing HttpContext which is what was recently introduced in Asp.Net-Core's IHttpContextAccessor
An implementation of the IHttpClientAcessor can look something like this
public class HttpClientAccessor : IHttpClientAccessor {
static readonly Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public HttpClient HttpClient { get { return client.Value; } }
}
So now for tests you can inject mock of the dependency.
If using a DI container remember to register the accessor as a singleton as well.

Categories

Resources