5
\$\begingroup\$

I have this simple setup for a .NET Core project which is just a very basic HttpClient usage to do some simple integration tests to a RESTful API. For now the test project remains separated from the main API project, that's why it's built on .NET Core for now, using NUnit as test framework as well. But because of that I have the freedom to change it anytime.

For now, the implementation is very simple, like this:

public static HttpClient _client1;
public static HttpClient _client2;

[OneTimeSetUp]
public void OneTimeSetUp()
{
    _client1 = new HttpClient {BaseAddress = new Uri("https://address1.com")};
    _client1.DefaultRequestHeaders.Add("API-KEY", "token_here");

    _client2 = new HttpClient {BaseAddress = new Uri("https://address2.com")};
    _client1.DefaultRequestHeaders.Add("API-KEY", "token_here");
}

//[OneTimeTearDown]
//public void OneTimeTearDown()
//{
    //_client1?.Dispose();
    //_client2?.Dispose();
//}

That's the main test class, but considering that later it will become a more robust test suite and it's going to be added on main project, I've found that I can use HttpClientFactory like this, which produces the same test results and I can modelate the services to be added to the API solution later.

public static HttpClient _client1;
public static HttpClient _client2;

[OneTimeSetUp]
public void OneTimeSetUp()
{
    var services = new ServiceCollection();

    services.AddHttpClient("Client1", client =>
    {
        client.BaseAddress = new Uri("https://address1.com/");
        client.DefaultRequestHeaders.Add("KEY", "token_here");
    });

    services.AddHttpClient("Client2", client =>
    {
        client.BaseAddress = new Uri("https://address2.com/");
        client.DefaultRequestHeaders.Add("KEY", "token_here");
    });

    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();

    _client1 = factory.CreateClient("Client1");
    _client2 = factory.CreateClient("Client2");
}

//[OneTimeTearDown]
//public void OneTimeTearDown()
//{
    //_client1?.Dispose();
    //_client2?.Dispose();
//}

My main problem is that we have two main API addresses that will be used under production environment and I'm not sure if this implementation is appropriated. Everything works as expected on both scenarios when called on tests...

[TestFixture, Parallelizable(ParallelScope.All)]
public class TestCase : Setup
{
    [Test]
    public async Task Get()
    {
        var response = await _client1.GetAsync("v1/Endpoint");
            response.EnsureSuccessStatusCode(); 
        var result = await response.Content.ReadAsStringAsync();

        Assert.NotNull(response);
        Console.WriteLine(JToken.Parse(result));
    }
}

So it will be the same for _client2 and in a lot of tests...

As I said, it works as expected on both ways, but since I'm new at this and didn't found much about this kind of integration test separated from the main API reference project, I'm not so sure about how I built it, so any other and more experienced views about it will be very much appreciated.

EDIT: I had the need to run all the tests in parallel, so I was generating an ObjectDisposedException with the Parallelizable attribute. Removing the OneTimeTearDown with the client disposing calls solved the problem, but I have doubts if that's the correct way to use HttpClient as well, as it still gives the expected results.

\$\endgroup\$
3
\$\begingroup\$

First of all I would like to say that, in my opinion, testing API in a such way is wrong, because how does such unit test behave when:

  • developer machine does not have Internet connection?
  • developer machine does not have proper certificates?
  • machine that run test does not have acces to such domain?
  • external API URL changed?
  • some new requirements (headers, parameters, body) occure (assume that external API is still under development)?
  • external API has Internet/electricity/domain problem?

The answer is simple - it will fail. And it could lead to BROKEN WINDOWS THEORY which basically means that someone can say "ok that unit tests does not pass, so we can ignore it" or even worse: "(...) add new failing tests!".

So if you want to test external API you should use some monitoring tool and health check it. If any problem occur - contact with API owner to make him solve the problem.

Secondly, you integrate external API with some kind of version - all responses are in JSON for example. The most important thing is to stub all its possible responses - correct ones with corrupted/correct data, without any data, empty response, timeouts, responses in other formats (HTML) etc. to test whether or not your code works correctly and predictable for all these cases. If API contract will change then new use cases you will add to your integration test.

My proposition is to wrap your communication method (HttpClient) into some kind of Facade, let's name it 'ICommunicatorFacade'. Let the parameters be single your custom Request, and let it return your custom Response type (which wraps what you need from HttpClient).

interface ICommunicatorFacade
{
    Task<CustomResponse> Get(CustomRequest request);
}

That you can easily stub with NSubstitute Moq and other libraries. Also you can check whether or not your facade received correct paramters and of course how does ICommunicator user (method that invokes Get method) behave for all use cases I mentioned above.

Good luck!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.