How to replace Middleware in integration tests pro

2020-08-25 06:14发布

问题:

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

回答1:

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]);
    }
}


回答2:

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.