Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace Middleware in integration tests project

I have startup cs where I register AuthenticationMiddleware like this:

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
        AddAuthentication(app);
        app.UseMvcWithDefaultRoute();
        app.UseStaticFiles();
    }

    protected virtual void AddAuthentication(IApplicationBuilder app)
    {
        app.UseAuthentication();
    }
}

and I test it using:

WebApplicationFactory<Startup>().CreateClient();

Question:

I would like to replace app.UseAuthentication(); with app.UseMiddleware<TestingAuthenticationMiddleware>(),

What I've tried:

I thought about inheriting from Startup in my test project:

public class TestStartup : Startup
{
    protected override void AddAuthentication(IApplicationBuilder app)
    {
        app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
    }
}

class TestWebApplicationFactory : WebApplicationFactory<Web.Startup>
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder()
            .UseStartup<IntegrationTestProject.TestStartup>();
    }
}

but this does not work, since TestStartup is in another assembly, which has a lot of side effects on WebHost.CreateDefaultBuilder()

I'm getting:

System.ArgumentException: The content root 'C:\Projects\Liero\myproject\tests\IntegrationTests' does not exist. Parameter name: contentRootPath

like image 504
Liero Avatar asked Sep 02 '18 11:09

Liero


People also ask

What are the four different ways that we can perform integration testing?

Common approaches to integration testing. Four key strategies to execute integration testing are big-bang, top-down, bottom-up and sandwich/hybrid testing. Each approach has benefits and drawbacks. Big-bang testing: The big-bang approach involves integrating all modules at once and testing them all as one unit.

Can integration tests replace unit tests?

Unit testing and integration testing are both important parts of successful software development. Although they serve different yet related purposes, one cannot replace the other. They complement each other nicely.


2 Answers

It seems that WebApplicationFactory should use the real Startup class as the type argument:

class TestWebApplicationFactory : WebApplicationFactory<Startup>
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder<TestableStartup>(new string[0]);
    }
}
like image 112
Liero Avatar answered Sep 20 '22 06:09

Liero


I have encountered the same issue and solved it like this;

 /// <summary>
    /// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
    /// </summary>
    /// <typeparam name="TStartup">Target project's startup type</typeparam>
    /// <typeparam name="DStartup">Decorated startup type</typeparam>
    public class TestFixture<DStartup, TStartup> : IDisposable
    {
        private readonly TestServer _server;

        public TestFixture()
            : this(Path.Combine("YourRelativeTargetProjectParentDir"))
        {
        }

        protected TestFixture(string relativeTargetProjectParentDir)
        {
            var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
            var contentRoot = GetProjectPath(relativeTargetProjectParentDir, startupAssembly);

            //var integrationTestsPath = PlatformServices.Default.Application.ApplicationBasePath;
            //var contentRoot = Path.GetFullPath(Path.Combine(integrationTestsPath, "../../../../MinasTirith"));
            var builder = new WebHostBuilder()
                .UseContentRoot(contentRoot)
                .ConfigureServices(InitializeServices)
                .UseEnvironment("Development")
                .UseStartup(typeof(DStartup));

            _server = new TestServer(builder);

            Client = _server.CreateClient();
            Client.BaseAddress = new Uri("http://localhost:5000");
        }

        public HttpClient Client { get; }   

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            Client.Dispose();
            _server.Dispose();
        }


        protected virtual void InitializeServices(IServiceCollection services)
        {
            var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;

            // Inject a custom application part manager. 
            // Overrides AddMvcCore() because it uses TryAdd().
            var manager = new ApplicationPartManager();
            manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
            manager.FeatureProviders.Add(new ControllerFeatureProvider());
            manager.FeatureProviders.Add(new ViewComponentFeatureProvider());

            services.AddSingleton(manager);
        }

        /// <summary>
        /// Gets the full path to the target project that we wish to test
        /// </summary>
        /// <param name="projectRelativePath">
        /// The parent directory of the target project.
        /// e.g. src, samples, test, or test/Websites
        /// </param>
        /// <param name="startupAssembly">The target project's assembly.</param>
        /// <returns>The full path to the target project.</returns>
        private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
        {
            // Get name of the target project which we want to test
            var projectName = startupAssembly.GetName().Name;

            // Get currently executing test project path
            var applicationBasePath = System.AppContext.BaseDirectory;

            // Find the path to the target project
            var directoryInfo = new DirectoryInfo(applicationBasePath);
            do
            {
                directoryInfo = directoryInfo.Parent;

                var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, projectRelativePath));
                if (projectDirectoryInfo.Exists)
                {
                    var projectFileInfo = new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj"));
                    if (projectFileInfo.Exists)
                    {
                        return Path.Combine(projectDirectoryInfo.FullName, projectName);
                    }
                }
            }
            while (directoryInfo.Parent != null);

            throw new Exception($"Project root could not be located using the application root {applicationBasePath}.");
        }
    }

And I use it in my unit test methods like this;

    [TestInitialize]
    public void BeforeEachTest()
    {
        testFixture = new TestFixture<TestStartup, Startup>();
        //this is my HttpClient variable
        client = testFixture.Client;
    }

P.S. This is the exact code snippet I use for my project.

like image 23
ilkerkaran Avatar answered Sep 20 '22 06:09

ilkerkaran