Reputation: 9013
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?
Upvotes: 0
Views: 79
Reputation: 32445
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
.
Upvotes: 0
Reputation: 17066
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.
Upvotes: 3