13

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

5
  • 2
    Note that the integration tests with the WebApplicationFactory are usually meant to test your whole application setup. By overwriting, disabling or changing certain parts, you are technically no longer integration testing your application but a different application which may or may not be helpful. Commented Sep 2, 2018 at 20:35
  • Have you tried using extension methods? I am working on a fairly big application and I have a separate assembly used for resolving dependencies and works just fine. In my implementation I "extend" IApplicationBuilder and IHostingEnvironment via extension methods to implement my custom middleware. It all can be reduced to app.ConfigureMiddleware() and all the configuration takes place inside that extension method. I hope this helps. Commented Sep 18, 2018 at 13:57
  • @poke: I agree, but how would you do integration testing of app, that requires OpenId Connect authentication? Commented Dec 21, 2018 at 9:40
  • I wouldn’t really test with OIDC since that usually requires user interaction with the authority (or a mocked authority). You could switch out the authenticatio provider for your integration tests so that you can explicitly set the identities. Commented Dec 21, 2018 at 10:16
  • @poke: yeah, so I "switched out the authentication provider" by replacing the OpenIDConnect middleware in the test project. Is there any better solution? Commented Dec 21, 2018 at 10:29

2 Answers 2

6

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]); } } 
Sign up to request clarification or add additional context in comments.

Comments

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.

2 Comments

Well, if I understand it correctly, you have replaced entire WebHostBuilder configuration. That probably works too, but it seems too heavy for my task.
@Liero, Actually, just target the ContentRoot and extend and override the actual startup.cs. I use this approach to test my API e2e. So yes, it may be overkill for a simple usage

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.