aspnetcore/src/Microsoft.AspNetCore.Mvc.Te.../WebApplicationTestFixture.cs

202 lines
9.4 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
namespace Microsoft.AspNetCore.Mvc.Testing
{
/// <summary>
/// Fixture for bootstrapping an application in memory for functional end to end tests.
/// </summary>
/// <typeparam name="TStartup">The applications startup class.</typeparam>
public class WebApplicationTestFixture<TStartup> : IDisposable where TStartup : class
{
private readonly TestServer _server;
/// <summary>
/// <para>
/// Creates a TestServer instance using the MVC application defined by<typeparamref name="TStartup"/>.
/// The startup code defined in <typeparamref name = "TStartup" /> will be executed to configure the application.
/// </para>
/// <para>
/// This constructor will infer the application root directive by searching for a solution file (*.sln) and then
/// appending the path<c>{AssemblyName}</c> to the solution directory.The application root directory will be
/// used to discover views and content files.
/// </para>
/// <para>
/// The application assemblies will be loaded from the dependency context of the assembly containing
/// <typeparamref name = "TStartup" />.This means that project dependencies of the assembly containing
/// <typeparamref name = "TStartup" /> will be loaded as application assemblies.
/// </para>
/// </summary>
public WebApplicationTestFixture()
: this(typeof(TStartup).Assembly.GetName().Name)
{
}
/// <summary>
/// <para>
/// Creates a TestServer instance using the MVC application defined by<typeparamref name="TStartup"/>.
/// The startup code defined in <typeparamref name = "TStartup" /> will be executed to configure the application.
/// </para>
/// <para>
/// This constructor will infer the application root directive by searching for a solution file (*.sln) and then
/// appending the path <paramref name="solutionRelativePath"/> to the solution directory.The application root
/// directory will be used to discover views and content files.
/// </para>
/// <para>
/// The application assemblies will be loaded from the dependency context of the assembly containing
/// <typeparamref name = "TStartup" />.This means that project dependencies of the assembly containing
/// <typeparamref name = "TStartup" /> will be loaded as application assemblies.
/// </para>
/// </summary>
/// <param name="solutionRelativePath">The path to the project folder relative to the solution file of your
/// application. The folder of the first .sln file found traversing up the folder hierarchy from the test execution
/// folder is considered as the base path.</param>
public WebApplicationTestFixture(string solutionRelativePath)
: this("*.sln", solutionRelativePath)
{
}
/// <summary>
/// <para>
/// Creates a TestServer instance using the MVC application defined by<typeparamref name="TStartup"/>.
/// The startup code defined in <typeparamref name = "TStartup" /> will be executed to configure the application.
/// </para>
/// <para>
/// This constructor will infer the application root directive by searching for a solution file that matches the pattern
/// <paramref name="solutionSearchPattern"/> and then appending the path <paramref name="solutionRelativePath"/>
/// to the solution directory.The application root directory will be used to discover views and content files.
/// </para>
/// <para>
/// The application assemblies will be loaded from the dependency context of the assembly containing
/// <typeparamref name = "TStartup" />.This means that project dependencies of the assembly containing
/// <typeparamref name = "TStartup" /> will be loaded as application assemblies.
/// </para>
/// </summary>
/// <param name="solutionSearchPattern">The glob pattern to use when searching for a solution file by
/// traversing up the folder hierarchy from the test execution folder.</param>
/// <param name="solutionRelativePath">The path to the project folder relative to the solution file of your
/// application. The folder of the first sln file that matches the <paramref name="solutionSearchPattern"/>
/// found traversing up the folder hierarchy from the test execution folder is considered as the base path.</param>
public WebApplicationTestFixture(string solutionSearchPattern, string solutionRelativePath)
{
EnsureDepsFile();
var builder = CreateWebHostBuilder();
builder
.UseStartup<TStartup>()
.UseSolutionRelativeContentRoot(solutionRelativePath);
ConfigureWebHost(builder);
_server = CreateServer(builder);
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
private void EnsureDepsFile()
{
var depsFileName = $"{typeof(TStartup).Assembly.GetName().Name}.deps.json";
var depsFile = new FileInfo(Path.Combine(AppContext.BaseDirectory, depsFileName));
if (!depsFile.Exists)
{
throw new InvalidOperationException(Resources.FormatMissingDepsFile(
depsFile.FullName,
Path.GetFileName(depsFile.FullName)));
}
}
/// <summary>
/// Creates a <see cref="IWebHostBuilder"/> used to setup <see cref="TestServer"/>.
/// <remarks>
/// The default implementation of this method looks for a <c>public static IWebHostBuilder CreateDefaultBuilder(string[] args)</c>
/// method defined on the entry point of the assembly of <typeparamref name="TStartup" /> and invokes it passing an empty string
/// array as arguments. In case this method can't be found,
/// </remarks>
/// </summary>
/// <returns>A <see cref="IWebHostBuilder"/> instance.</returns>
protected virtual IWebHostBuilder CreateWebHostBuilder() =>
WebHostBuilderFactory.CreateFromTypesAssemblyEntryPoint<TStartup>(Array.Empty<string>()) ?? new WebHostBuilder();
/// <summary>
/// Creates the <see cref="TestServer"/> with the bootstrapped application in <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> used to
/// create the server.</param>
/// <returns>The <see cref="TestServer"/> with the bootstrapped application.</returns>
protected virtual TestServer CreateServer(IWebHostBuilder builder) => new TestServer(builder);
/// <summary>
/// Gives a fixture an opportunity to configure the application before it gets built.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> for the application.</param>
protected virtual void ConfigureWebHost(IWebHostBuilder builder)
{
}
/// <summary>
/// Gets an instance of the <see cref="HttpClient"/> used to send <see cref="HttpRequestMessage"/> to the server.
/// </summary>
public HttpClient Client { get; }
/// <summary>
/// Creates a new instance of an <see cref="HttpClient"/> that can be used to
/// send <see cref="HttpRequestMessage"/> to the server.
/// </summary>
/// <returns>The <see cref="HttpClient"/></returns>
public HttpClient CreateClient()
{
var client = _server.CreateClient();
client.BaseAddress = new Uri("http://localhost");
return client;
}
/// <summary>
/// Creates a new instance of an <see cref="HttpClient"/> that can be used to
/// send <see cref="HttpRequestMessage"/> to the server.
/// </summary>
/// <param name="baseAddress">The base address of the <see cref="HttpClient"/> instance.</param>
/// <param name="handlers">A list of <see cref="DelegatingHandler"/> instances to setup on the
/// <see cref="HttpClient"/>.</param>
/// <returns>The <see cref="HttpClient"/>.</returns>
public HttpClient CreateClient(Uri baseAddress, params DelegatingHandler[] handlers)
{
if (handlers == null || handlers.Length == 0)
{
var client = _server.CreateClient();
client.BaseAddress = baseAddress;
return client;
}
else
{
for (var i = handlers.Length - 1; i > 1; i--)
{
handlers[i - 1].InnerHandler = handlers[i];
}
var serverHandler = _server.CreateHandler();
handlers[handlers.Length - 1].InnerHandler = serverHandler;
var client = new HttpClient(handlers[0]);
client.BaseAddress = baseAddress;
return client;
}
}
/// <inheritdoc />
public void Dispose()
{
Client.Dispose();
_server.Dispose();
}
}
}