I\'m adding some unit tests for my ASP.NET Core Web API, and I\'m wondering whether to unit test the controllers directly or through an HTTP client. Directly would look roughly
TL;DR
Is it best practice to test [...] directly or through an HTTP client?
Not "or" but "and". If you serious about best practices of testing - you need both tests.
First test is a unit test. But the second one is an integration test.
There is a common consensus (test pyramid) that you need more unit tests comparing to the number of integration tests. But you need both.
There are many reasons why you should prefer unit tests over integration tests, most of them boil down to the fact that unit test are small (in all senses) and integration tests - aren't. But the main 4 are:
Locality
When your unit test fails, usually, just from it's name you can figure out the place where the bug is. When integration test becomes red, you can't say right away where is the issue. Maybe it's in the controller.GetGroups
or it's in the HttpClient
, or there is some issue with the network.
Also, when you introduce a bug in your code it's quite possible that only one of unit tests will become red, while with integration tests there are more chances that more than one of them will fail.
Stability
With a small project which you can test on you local box you probably won't notice it. But on a big project with distributed infrastructure you will see blinking tests all the time. And that will become a problem. At some point you can find yourself not trusting test results anymore.
Speed
With a small project with a small number of tests you won't notice it. But on a bit project it will become a problem. (Network delays, IO delays, initialization, cleanup, etc., etc.)
Simplicity
You've noticed it yourself.
But that not always true. If you code is poorly structured, then it's easier to write integration tests. And that's one more reason why you should prefer unit tests. In some way they force you to write more modular code (and I'm not taking about Dependency Injection).
But also keep in mind that best practices are almost always about big projects. If your project is small, and will stay small, there are a big chance that you'll be better off with strictly opposite decisions.
Write more tests. (Again, that means - both). Become better at writing tests. Delete them latter.
Practice makes perfect.
When doing unit test, it is important to know what are you going to test and write the tests based on your requirements. However, the second test, might looks like an integration test instead of an unit test, but I do not care to this point now!
Between your tests, I would recommend you to use the second option, because in the second unit test, you are testing your WebApi, as an WebApi, not as a class. For example suppose that you have a class with a method named X()
. So how likely is it to write an unit test for it using Reflection? If it is completely unlikely, then writing an unit test based on Reflection is a waste of time. If it is likely, so you should write your test using Reflection too.
Moreover, using the second approach you are able to change the tech stack(For replace .Net with php) used for producing the WebApi, without changing you tests(This is what we expect from a WebApi too).
Finally, you should make a decision! how are you going to use this WebApi? How likely is it to call your WebApi using direct class instantiating?
Note:
It might be irrelevant to your question, but you should concentrate on your Asserts, too. For example asserting ResponseStatusCode
and ResponseStatusMsg
might not be needed and you can assert only one.
Or what will happen if obj
is null? or obj
has more than one member?
You can use Swagger (aka OpenAPI).
Install Swashbuckle.AspNetCore from nuget.
using Microsoft.OpenApi.Models;
//in Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
}
//in Startup.Configure
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
Finally, add "launchUrl": "swagger", in launchSettings.json
If you are looking for some non programming You can use Postman, and can create collection of requests and can test multiple requests one by one.
If we limit the scope of discussion to Controller vs HttpClient testing comparison, I would say that it is better to use HttpClient. Because if you write tests for your controllers, you're already writing integration tests already and there is almost no point to write "weaker" integration tests while you can write stronger ones that is more realistic and also superset of the weaker ones.
For example, you can see from your own example that both of your test are testing exactly the same functionality. The different is that the latter one cover more area of testing -- JSON response, or can be something else like HTTP header you want to test. If you write the latter test, you don't need the first test at all.
I understand the pain of how to inject mocked dependencies. This requires more effort comparing to testing controller directly. However, .NET Core already provides a good set of tools to help you on that. You can setup the test host inside the test itself, configure it and get HttpClient from it. Then you can use that HttpClient for your testing purpose.
The other concern is that it is quite a tedious task to craft HttpClient's request for each test. Anyway, Refit can help you a lot on this. Refit's declarative syntax is quite easy to understand (and maintain eventually). While I would also recommend Refit for all remote API calls, it is also suitable for ASP.NET Core integration testing.
Combining all solutions available, I don't see why you should limit to controller test while you can go for more "real" integration test with only some little more effort.
I'd say that they are not mutually exclusive. The first option is a classical unit test while the second is an integration test as involves more than a single unit of code.
If I had time to write either unit tests or integration tests, I'd pick unit tests as provides a more focused approach and gives, at least in my opinion, the best result from cost benefit.
In some particular projects where I had enough resources to write different suites of tests, I wrote both tests covering approaches. Where the second one would run without mocking anything (or maybe just the persistent storage) so I could test how all the components integrate together.
In relation to good practices, if you want to do real unit test, then you have no option but picking option one as no external dependencies are allowed (HttpClient
is an external dependency).
Then, if the time and resources allow it, you could do integration testing for the most critical and/or complex paths.