Integration Testing in ASP.NET Core with WebApplicationFactory

Integration testing is a crucial step in ensuring that your application behaves correctly when different components interact with one another. In ASP.NET Core, the WebApplicationFactory<TEntryPoint> class makes it easier to test your web applications by allowing you to spin up an instance of your app in-memory and run integration tests against it.

As a new author contributing to the VSLive! Blog, in my inaugural post I'll walk you through how to use WebApplicationFactory for integration testing, using a simple ASP.NET Core web application. We'll write tests to ensure that the Home page is functioning as expected. Let's dive in!

Structure of the Sample Project
This post includes a code sample with "before" and "after" versions of the code.

[Click on image for larger view.]

In the solution shown above we have a total of four projects. We have two main projects:

  1. API Project: A .NET Core class library containing business logic.
  2. Web Project: An ASP.NET MVC application with the front-end logic.

Additionally, we have two test projects:

  1. Unit Tests: For testing the API logic in isolation.
  2. Integration Tests: For testing interactions between components, such as controllers and views, within the actual application.

We'll focus on the Integration Tests project, using WebApplicationFactory to create tests that check if the web application behaves correctly in real-world scenarios.

What Is WebApplicationFactory?
WebApplicationFactory<TEntryPoint> is a handy class in ASP.NET Core that allows us to spin up our application in-memory for testing purposes. This eliminates the need for dealing with network ports or external dependencies during testing. By creating an in-memory version of the application, we can run HTTP requests against it, similar to how we would interact with the deployed app.

Creating an Integration Test
Let's create a test to verify that the Home page of our web application displays the text "Hello World." To get started, open your Integration Test project, right-click, and add a new class. We'll name it HomeControllerFixture.

Writing the Test -- Here's the basic structure of our test:

public async void IndexContainsHelloWorld()
{
    // arrange
    var factory = new WebApplicationFactory<JustAnEmptyClass>();
    var client = factory.CreateClient();

    // act
    var response = await client.GetAsync("/");

    // assert
    Assert.NotNull(response);

    var content = await response.Content.ReadAsStringAsync();
    Assert.Contains("Hello, world!", content);

    Assert.True(response.IsSuccessStatusCode);
}

Breakdown of the Test:

  • Arrange:
    • We create an instance of WebApplicationFactory<JustAnEmptyClass> and use it to spin up an in-memory version of our app.
    • Using CreateClient(), we get an instance of HttpClient that knows how to communicate with the in-memory application.
  • Act:
    • The client.GetAsync("/") method makes an HTTP GET request to the root of the application, similar to navigating to the home page in a browser.
  • Assert:
    • We use EnsureSuccessStatusCode() to check if the request was successful (i.e., it returned a 200 status code).
    • We then read the response content and check that it contains the string "Hello World" using Assert.Contains().

    Handling the Missing Startup Class
    You might notice that we reference a class called JustAnEmptyClass instead of the Startup class. Here's why: In older ASP.NET Core projects, the Startup.cs file was used to configure the application. However, in newer project templates, the Program.cs file has taken over this role, combining the functionality of both Program and Startup. This is all part of the change to add "top-level statements" to C#. On the one hand, it simplifies the code. On the other hand, it sometimes gets in the way, and this is one of those cases where it gets in the way.

    If your project actually has a Startup.cs class, then you can use that (WebApplicationFactory) instead of creating this empty, marker class. Or if you'd like to have your project actually use that style of project you can easily do that by adding the --use-program-main option to your dotnet new command when you create your project.

    Unfortunately, WebApplicationFactory doesn't work directly with the Program class. The workaround is simple: create an empty class and use it as a placeholder for WebApplicationFactory. This doesn't interfere with the functionality of your application but allows you to run your tests.

    // JustAnEmptyClass.cs
    public class JustAnEmptyClass
    {
        // This class is intentionally left empty.
    
    }

    Now, when creating the factory, we reference JustAnEmptyClass instead of Startup or Program.

    Running the Tests: It Fails
    After writing the test, run it from your Test Explorer window. If you encounter an error that the test is missing, check that you've added the [Fact] attribute to the test method -- this tells the test framework that it's a test that should be run.

    Upon running the test, it should fail initially because the home page doesn't yet contain the string "Hello World."

    [Click on image for larger view.]

    Fixing the Failure
    In our sample application, fixing this problem is easy enough. There's nothing on the home page that says "hello world" and therefore we just need to add that. In real-life development situations, you'll probably be testing for more complex things and you might need to write some real code.

    To fix this, let's add "Hello World" to our Home page.

    [Click on image for larger view.]
    1. In Solution Explorer, open up the web application and go to the Views folder and then the Home folder.
    2. Open Index.cshtml.
    3. Add a paragraph containing "Hello World."
    4. Save the file and rerun the test.

    When you've made the changes to Index.cshtml, it should look something like this:

    [Click on image for larger view.]

    This time, when you run the tests, the test should pass, confirming that our integration test successfully verifies the content of the home page.

    [Click on image for larger view.]

    Conclusion
    Using WebApplicationFactory, we can easily write integration tests that spin up an in-memory version of our ASP.NET Core application. This allows us to test the interaction between controllers, views, and other components without needing to deploy the app to a server.

    In this demo, we wrote a test to check that the Home page contains "Hello World," but you can extend this approach to test more complex scenarios like API endpoints, form submissions and more.

    Posted by Benjamin Day on 09/23/2024


    Keep Up-to-Date with Visual Studio Live!

    Email address*Country*