DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Identifying, Exploiting, and Preventing Host Header Attacks on Web Servers
  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Build a Simple Chat Server With gRPC in .Net Core
  • Deploy an ASP.NET Core Application in the IBM Cloud Code Engine

Trending

  • It’s Not About Control — It’s About Collaboration Between Architecture and Security
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  • A Modern Stack for Building Scalable Systems
  1. DZone
  2. Data Engineering
  3. Data
  4. Using a Custom Startup Class With ASP.NET Core Integration Tests

Using a Custom Startup Class With ASP.NET Core Integration Tests

We take a look at how custom startup classes can help make better integration tests, and how to implement this in your code.

By 
Gunnar Peipman user avatar
Gunnar Peipman
·
Apr. 12, 19 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
27.8K Views

Join the DZone community and get the full member experience.

Join For Free

My previous post demonstrated how to use a custom appsettings.js file with integration tests in ASP.NET Core. But in practice it's not enough and very often we need a custom startup class that extends the one in the web application project to configure the application for integration tests. This blog post shows how to do this.

Getting Started

Using a custom startup class is a little bit tricky. I start again with a simple, classic integration test from the ASP.NET Core integration testing documentation. 

public class HomeControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;
 
    public HomeControllerTests(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }
 
    [Theory]
    [InlineData("/")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();
 
        // Act
        var response = await client.GetAsync(url);
 
        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString());
    }
}

Making Members of the Startup Class Virtual

To avoid all kinds of confusion with method calls, we have to make the methods in the web application's startup class virtual.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
 
    public IConfiguration Configuration { get; }
 
    public virtual void ConfigureServices(IServiceCollection services)
    { 
        // Configure services
        // Configure dependency injection
    }
 
    public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // Configure application
    }
}

If there's only one method to override then other methods in the Startup class can be non-virtual.

Adding a Custom Startup Class

Now let's create a custom startup class for the integration tests project. I call it FakeStartup. It extends the Startup class of our web application and overrides the Configure() method.

public class FakeStartup : Startup
{
    public FakeStartup(IConfiguration configuration) : base(configuration)
    {
    }
 
    public override void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        base.Configure(app, env, loggerFactory);
 
        var serviceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
        using (var serviceScope = serviceScopeFactory.CreateScope())
        {
            var dbContext = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
 
            if (dbContext.Database.GetDbConnection().ConnectionString.ToLower().Contains("database.windows.net"))
            {
                throw new Exception("LIVE SETTINGS IN TESTS!");
            }
 
            // Initialize database
        }
    }
}

I didn't add much logic to the  Configure() method of the FakeStartup class. There's just one check to make sure that the SQL Server connection string doesn't point to a live database. And there's also room left for database initialization.

Custom Web Application Factory

To make the test host use our fake startup class with the web application we need to apply some magic. First, we need a custom web application factory that provides integration test mechanisms with a custom web host builder. Here is my application factory - fairly simple and very general.

public class MediaGalleryFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint : class
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder(null)
                      .UseStartup<TEntryPoint>();
    }
}

NB! The configuration built for a web host in the method above must contain the same services as specified in the program.cs file of the web application. To avoid synchronizing changes between web host builders in web applications and integration test projects, we can use some general methods for configuring web hosts or moving the  configuration to the Startup class.

Configuring the Integration Test

Using this custom web application factory doesn't come for free. We have to solve some issues when switching over to the fake startup class:

  1. Custom location for a web host builder confuses the integration test mechanism and we have to point out the correct location of the web application's content root (this is a folder in the web application).
  2. Related to previous point, we have to tell that the web application assembly is the one where application parts, like controllers, views, etc., must be searched for.

The simple integration test form the beginning of this post is given below, only now with support for the custom startup class.

public class HomeControllerTests : IClassFixture<MediaGalleryFactory<FakeStartup>>
{
    private readonly WebApplicationFactory<FakeStartup> _factory;
 
    public HomeControllerTests(MediaGalleryFactory<FakeStartup> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.UseSolutionRelativeContentRoot("MediaGallery");
 
            builder.ConfigureTestServices(services =>
            {
                services.AddMvc().AddApplicationPart(typeof(Startup).Assembly);
            });
                
        });
    }
 
    [Theory]
    [InlineData("/")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();
 
        // Act
        var response = await client.GetAsync(url);
 
        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString());
    }
}

Now we are done with custom startup class but let's go a little further.

Using a Custom appsettings.js File for Integration Tests

When I run this test I get the following error. Remember what we did in custom startup class?

Integration Tests

To solve this issue we have to go back to my previous blog post using a custom appsettings.json file with ASP.NET Core integration tests. We need the appsettings.json file where the database connection string and other settings are defined for integration tests.

After introducing appsettings.json for integration tests our test class is complete.

public class HomeControllerTests : IClassFixture<MediaGalleryFactory<FakeStartup>>
{
    private readonly WebApplicationFactory<FakeStartup> _factory;
 
    public HomeControllerTests(MediaGalleryFactory<FakeStartup> factory)
    {
        var projectDir = Directory.GetCurrentDirectory();
        var configPath = Path.Combine(projectDir, "appsettings.json");
 
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.UseSolutionRelativeContentRoot("MediaGallery");
 
            builder.ConfigureAppConfiguration(conf =>
            {
                conf.AddJsonFile(configPath);
            });
 
            builder.ConfigureTestServices(services =>
            {
                services.AddMvc().AddApplicationPart(typeof(Startup).Assembly);
            });                
        });
    }
 
    [Theory]
    [InlineData("/")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();
 
        // Act
        var response = await client.GetAsync(url);
 
        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString());
    }
}

Wrapping Up

A custom startup class for integration tests may be useful when integration tests need additional configuring and we need to do it when web application is configuring. My simple custom startup class demonstrated how to fail tests when integration tests are using live configuration. It's also possible to use a custom startup class to configure services using integration test-specific options. Getting a custom startup class to work was challenging but it's actually easy once it's done, as the amount of new code is small.

integration test ASP.NET ASP.NET Core Web application application Web Service Database connection Host (Unix)

Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Identifying, Exploiting, and Preventing Host Header Attacks on Web Servers
  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Build a Simple Chat Server With gRPC in .Net Core
  • Deploy an ASP.NET Core Application in the IBM Cloud Code Engine

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

OSZAR »