0

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?

Oleg Sh
  • 8,496
  • 17
  • 89
  • 159

2 Answers2

3

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.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • I can't use DI in such case. In any case, in dependency injection composition root we have to configure `IConfiguration` and `HttpMessageHandler`... – Oleg Sh Feb 20 '19 at 00:15
  • Dependencies on concrete classes create difficulty for unit testing. [Programming to interfaces](https://stackoverflow.com/q/383947/1371329) helps to alleviate that difficulty and facilitate simpler unit tests. – jaco0646 Feb 20 '19 at 00:25
  • I know about DI. Probably, not good example. But in composition root we will have the same problem – Oleg Sh Feb 20 '19 at 02:42
  • When `AddMxRecordToRegistrator` depends only on `IDomainActions` the former can be unit tested simply by mocking the latter. – jaco0646 Feb 20 '19 at 03:11
0

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.

Fabio
  • 31,528
  • 4
  • 33
  • 72