Search code examples
c#integration-testingasp.net-core-webapi

ASP.NET Web API Test instance initialization


I am writing some integration tests for my web API, which means that it has to be running during the execution of the tests. Is there any way to run it with an in-memory database instead of a real one based on SQL Server?

Also, I need to run a few instances at a time, so I need somehow to change the base address of each of them to be unique. For example, I could append to the base URL these instance IDs, that are mentioned in the code below.

Here is the code which I am using to run a new instance for my tests:

public static class WebApiHelper
{
    private const string ExecutableFileExtension = "exe";

    private static readonly Dictionary<Guid, Process> _instances = new();

    public static void EnsureIsRunning(Assembly? assembly, Guid instanceId)
    {
        if (assembly is null)
            throw new ArgumentNullException(nameof(assembly));

        var executableFullName = Path.ChangeExtension(
            assembly.Location, ExecutableFileExtension);

        _instances.Add(instanceId, Process.Start(executableFullName));
    }

    public static void EnsureIsNotRunning(Guid instaceId)
        => _instances[instaceId].Kill();
}

Talking in general, is this a good way to create test instances, or maybe I am missing something? Asking this, because maybe there is another 'legal' way to achieve my goal.


Solution

  • Okay, so in the end, I came up with this super easy and obvious solution.

    As was mentioned in the comments - using the in-memory database is not the best way to test, because relational features are not supported if using MS SQL. So I decided to go another way.

    Step 1: Overwrite the connection strings.

    In my case, that was easy since I have a static IConfiguration instance and was need just to overwrite the connection string within that instance.

    The method looks as follows:

    private const string ConnectionStringsSectionName = "ConnectionStrings";
    
    private const string TestConnectionStringFormat = "{0}_Test";
    
    private static bool _connectionStringsOverwitten;
    
    private static void OverwriteConnectionStrings()
    {
        if (_connectionStringsOverwitten)
            return;
    
        var connectionStrings = MyStaticConfigurationContainer.Configuration
            .AsEnumerable()
            .Where(entry => entry.Key.StartsWith(ConnectionStringsSectionName)
                         && entry.Value is not null);
    
        foreach (var connectionString in connectionStrings)
        {
            var builder = new SqlConnectionStringBuilder(connectionString.Value);
    
            builder.InitialCatalog = string.Format(TestConnectionStringFormat,
                                                   builder.InitialCatalog);
    
            MyStaticConfigurationContainer.Configuration[connectionString.Key] = builder.ConnectionString;
        }
    
        _connectionStringsOverwitten = true;
    }
    

    Of course, you would need to handle the database creation and deletion before and after running the tests, otherwise - your test DBs may become a mess.

    Step 2: Simply run your web API instance within a separate thread.

    In my case, I am using the NUnit test framework, which means I just need to implement the web API setup logic within the fixture. Basically, the process would be more or less the same for every testing framework.

    The code looks as follows:

    [SetUpFixture]
    public class WebApiSetupFixture
    {
        private const string WebApiThreadName = "WebApi";
    
        [OneTimeSetUp]
        public void SetUp() => new Thread(RunWebApi)
        {
            Name = WebApiThreadName
        }.Start();
    
        private static void RunWebApi()
            => Program.Main(Array.Empty<string>());
        // 'Program' - your main web app class with entry point.
    }
    

    Note: The code inside Program.Main(); will also look for connection strings in the MyStaticConfigurationContainer.Configuration which was changed in the previous step.

    And that's it! Hope this could help somebody else :)