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

146 lines
6.9 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Testing.Internal;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.Testing
{
/// <summary>
/// Builder API for bootstraping an MVC application for functional tests.
/// </summary>
/// <typeparam name="TStartup">The application startup class.</typeparam>
public class MvcWebApplicationBuilder<TStartup> where TStartup : class
{
public string ContentRoot { get; set; }
public IList<Action<IServiceCollection>> ConfigureServicesBeforeStartup { get; set; } = new List<Action<IServiceCollection>>();
public IList<Action<IServiceCollection>> ConfigureServicesAfterStartup { get; set; } = new List<Action<IServiceCollection>>();
public List<Assembly> ApplicationAssemblies { get; set; } = new List<Assembly>();
/// <summary>
/// Configures services before TStartup.ConfigureServices runs.
/// </summary>
/// <param name="configure">The <see cref="Action{IServiceCollection}"/> to configure the services with.</param>
/// <returns>An instance of this <see cref="MvcWebApplicationBuilder{TStartup}"/></returns>
public MvcWebApplicationBuilder<TStartup> ConfigureBeforeStartup(Action<IServiceCollection> configure)
{
ConfigureServicesBeforeStartup.Add(configure);
return this;
}
/// <summary>
/// Configures services after TStartup.ConfigureServices runs.
/// </summary>
/// <param name="configure">The <see cref="Action{IServiceCollection}"/> to configure the services with.</param>
/// <returns>An instance of this <see cref="MvcWebApplicationBuilder{TStartup}"/></returns>
public MvcWebApplicationBuilder<TStartup> ConfigureAfterStartup(Action<IServiceCollection> configure)
{
ConfigureServicesAfterStartup.Add(configure);
return this;
}
/// <summary>
/// Configures <see cref="ApplicationPartManager"/> to include the default set
/// of <see cref="ApplicationPart"/> provided by <see cref="DefaultAssemblyPartDiscoveryProvider"/>.
/// </summary>
/// <returns>An instance of this <see cref="MvcWebApplicationBuilder{TStartup}"/></returns>
public MvcWebApplicationBuilder<TStartup> UseApplicationAssemblies()
{
var depsFileName = $"{typeof(TStartup).Assembly.GetName().Name}.deps.json";
var depsFile = new FileInfo(Path.Combine(AppContext.BaseDirectory, depsFileName));
if (!depsFile.Exists)
{
throw new InvalidOperationException($"Can't find'{depsFile.FullName}'. This file is required for functional tests " +
"to run properly. There should be a copy of the file on your source project bin folder. If thats not the " +
"case, make sure that the property PreserveCompilationContext is set to true on your project file. E.g" +
"'<PreserveCompilationContext>true</PreserveCompilationContext>'." +
$"For functional tests to work they need to either run from the build output folder or the {Path.GetFileName(depsFile.FullName)} " +
$"file from your application's output directory must be copied" +
"to the folder where the tests are running on. A common cause for this error is having shadow copying enabled when the " +
"tests run.");
}
ApplicationAssemblies.AddRange(DefaultAssemblyPartDiscoveryProvider
.DiscoverAssemblyParts(typeof(TStartup).Assembly.GetName().Name)
.Select(s => ((AssemblyPart)s).Assembly)
.ToList());
return this;
}
/// <summary>
/// Configures the application content root.
/// </summary>
/// <param name="solutionName">The glob pattern to use for finding the solution.</param>
/// <param name="solutionRelativePath">The relative path to the content root from the solution file.</param>
/// <returns>An instance of this <see cref="MvcWebApplicationBuilder{TStartup}"/></returns>
public MvcWebApplicationBuilder<TStartup> UseSolutionRelativeContentRoot(
string solutionRelativePath,
string solutionName = "*.sln")
{
if (solutionRelativePath == null)
{
throw new ArgumentNullException(nameof(solutionRelativePath));
}
var applicationBasePath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
if (solutionPath != null)
{
ContentRoot = Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath));
return this;
}
directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null);
throw new Exception($"Solution root could not be located using application root {applicationBasePath}.");
}
public TestServer Build()
{
var builder = new WebHostBuilder()
.UseStartup<TestStartup<TStartup>>()
// This is necessary so that IHostingEnvironment.ApplicationName has the right
// value and libraries depending on it (to load the dependency context, for example)
// work properly.
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TStartup).Assembly.GetName().Name)
.UseContentRoot(ContentRoot)
.ConfigureServices(InitializeServices);
return new TestServer(builder);
}
protected virtual void InitializeServices(IServiceCollection services)
{
// Inject a custom application part manager. Overrides AddMvcCore() because that uses TryAdd().
var manager = new ApplicationPartManager();
foreach (var assembly in ApplicationAssemblies)
{
manager.ApplicationParts.Add(new AssemblyPart(assembly));
}
services.AddSingleton(manager);
services.AddSingleton(new TestServiceRegistrations
{
Before = ConfigureServicesBeforeStartup,
After = ConfigureServicesAfterStartup
});
}
}
}