DEV Community

loading...
Cover image for C# Web API: How to call your endpoint through integration tests

C# Web API: How to call your endpoint through integration tests

Willian Antunes
I am an eternal seeker of curiosity. Visit my blog to know more.
Originally published at willianantunes.com ・4 min read

It's fantastic when you have all of your unit tests returning green signs everywhere. Still, when you execute your project, it raises an error because an infrastructure setup is wrong, making it impossible to run your Web API properly 😑. It can be how you set up your logs, database, providers, well, many other things. One approach to fix it or minimize its impact is through integration tests. So how can you do a quick setup for that 🤔? By the way, in this blog post, we're going to consider the following technologies:

Describing the sample project

Here's the image that shows a sample flow:

This flow has a person who calls the API, which does something and then returns the answer to the user.

It is composed of three steps:

  1. The user calls the endpoint /api/v1/movies.
  2. The application will do fake processing.
  3. A random movie is returned to the user.

To take care of this business rule, here's our controller:

[ApiController]
[Route("api/v1/[controller]")]
public class MoviesController : ControllerBase
{
    private readonly IFilmSpecialist _filmSpecialist;

    public MoviesController(IFilmSpecialist filmSpecialist)
    {
        _filmSpecialist = filmSpecialist;
    }

    [HttpGet]
    public Movie Get()
    {
        Log.Information("Let me ask the film specialist...");
        var movie = _filmSpecialist.SuggestSomeMovie();
        Log.Information("Suggested movie: {Movie}", movie);
        return movie;
    }
}
Enter fullscreen mode Exit fullscreen mode

Who will be responsible for doing fake processing:

public class FilmSpecialist : IFilmSpecialist
{
    private static readonly Movie[] Films =
    {
        new("RoboCop", "10/08/1987", new[] {"Action", "Thriller", "Science Fiction"}, "1h 42m"),
        new("The Matrix", "05/21/1999", new[] {"Action", "Science Fiction"}, "2h 16m"),
        new("Soul", "12/25/2020", new[] {"Family", "Animation", "Comedy", "Drama", "Music", "Fantasy"}, "1h 41m"),
        new("Space Jam", "12/25/1996", new[] {"Adventure", "Animation", "Comedy", "Family"}, "1h 28m"),
        new("Aladdin", "07/03/1993", new[] {"Animation", "Family", "Adventure", "Fantasy", "Romance"}, "1h 28m"),
        new("The World of Dragon Ball Z", "01/21/2000", new[] {"Action"}, "20m"),
    };

    public Movie SuggestSomeMovie()
    {
        Log.Debug("OKAY! Which film will I suggest 🤔");
        Random random = new();
        var filmIndexThatIWillSuggest = random.Next(0, Films.Length);
        Log.Information("Will suggest the film with index {FilmIndex}!", filmIndexThatIWillSuggest);

        return Films[filmIndexThatIWillSuggest];
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's first do the unit testing to ensure that our methods contracts are being respected.

Starting from unit testing

Here we'll do a simple unit test on the service responsible for returning a random movie. We can write something like the following, as it's not our focus:

public class FilmSpecialistTests
{
    private readonly IFilmSpecialist _filmSpecialist = new FilmSpecialist();

    [Fact]
    public void ShouldReturnRandomMovieWhenAsked()
    {
        // Act
        var suggestedMovie = _filmSpecialist.SuggestSomeMovie();
        // Assert
        var expectedTiles = new[]
        {
            "RoboCop", "The Matrix", "Soul", "Space Jam", "Aladdin", "The World of Dragon Ball Z"
        };
        suggestedMovie.Title.Should().BeOneOf(expectedTiles);
    }
}
Enter fullscreen mode Exit fullscreen mode

Making an actual HTTP request to our API

To call our endpoint, we can use a class fixture with the help of WebApplicationFactory (know more about it at the section Basic tests with the default WebApplicationFactory in Integration tests in ASP.NET Core guide). In our class test constructor, we can use the factory to create a HttpClient, hence allowing us to do HTTP calls to our endpoint. Moreover, let's say you'd like to replace an injected service with a mock: you can do that through ConfigureTestServices. To illustrate a complete example:

public class MoviesControllerITests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly IFilmSpecialist _filmSpecialist;
    private HttpClient _httpClient;

    public MoviesControllerITests(WebApplicationFactory<Startup> factory)
    {
        _filmSpecialist = Mock.Of<IFilmSpecialist>();
        _httpClient = factory.WithWebHostBuilder(builder =>
        {
            // https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0#inject-mock-services
            builder.ConfigureTestServices(services =>
            {
                services.RemoveAll<IFilmSpecialist>();
                services.TryAddTransient(_ => _filmSpecialist);
            });
        }).CreateClient();
    }

    [Fact]
    public async Task ShouldCreateGameGivenFirstMovementIsBeingExecuted()
    {
        // Arrange
        var requestPath = "/api/v1/movies";
        var movieToBeSuggested = new Movie("Schindler's List", "12/31/1993", new[] {"Drama", "History", "War"}, "3h 15m");
        Mock.Get(_filmSpecialist)
            .Setup(f => f.SuggestSomeMovie())
            .Returns(movieToBeSuggested)
            .Verifiable();
        // Act
        var response = await _httpClient.GetAsync(requestPath);
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var movie = await response.Content.ReadFromJsonAsync<Movie>();
        // Assert
        movie.Should().BeEquivalentTo(movieToBeSuggested);
        Mock.Get(_filmSpecialist).Verify();
    }
}
Enter fullscreen mode Exit fullscreen mode

By the way, to use WebApplicationFactory, you must install the package:

Microsoft.AspNetCore.Mvc.Testing
Enter fullscreen mode Exit fullscreen mode

It's pretty simple 🤗. I'll leave it as it is, but we could abstract our integration test to avoid creating a HttpClient every time for each of our class tests 😏.

To end things off

I think you can get enormous benefits from doing a cheap integration test sometimes because, as I told you at the beginning, it can almost guarantee that your code will be shipped as expected at the infrastructure layer. In this article, I gave a somewhat simple example, but things can be more challenging, let's say when it comes to broker connection — the subject of another blog entry 😜.

You can consult the code I showed here on this GitHub repository. You can use Docker Compose to run the project as well as execute its tests. Check the README for more details.

Posted listening to Toy Soldiers, Martika.

Discussion (5)

Collapse
davidkroell profile image
David Kröll

Very insightful!

Did not know that there exists such a integration testing library already. I created my own - but it works quite the same.

Collapse
fakhrulhilal profile image
Fakhrulhilal Maktum

It’s called integration testing, not unit test anymore. The benefit of it, it simulates API call. The cons, not all code coverage tool can identify it. So we need to figure out our self or it will remain uncovered.

Collapse
willianantunes profile image
Willian Antunes Author

Hi David! Actually, I had to create my own as well. Basically, I needed many customizations, like creating a database to handle parallel testing for each method test. So I wrote down a bit of the detail regarding it in this Tic Tac Toe through API repository.

Collapse
davidkroell profile image
David Kröll

Oh, glad to hear... thought of much unnecessary work done upfront 👍

Collapse
fakhrulhilal profile image
Fakhrulhilal Maktum

You don’t need to remove previous registration to mock. I always do this in my project.