Oleg Sh
Oleg Sh

Reputation: 9013

Unit Tests and incapsulation

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

Answers (2)

Fabio
Fabio

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

jaco0646
jaco0646

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

Related Questions