[Fixes #6233] Productionize and harden our functional testing infrastructure
This commit is contained in:
parent
aa5a348385
commit
35152d5933
35
Mvc.sln
35
Mvc.sln
|
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.9
|
||||
VisualStudioVersion = 15.0.26615.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
|
@ -122,6 +122,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesWebSite", "test\W
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Performance", "test\Microsoft.AspNetCore.Mvc.Performance\Microsoft.AspNetCore.Mvc.Performance.csproj", "{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{7500B228-1769-4CFB-A571-3DFAC6678A06}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Testing.Xunit", "src\Microsoft.AspNetCore.Mvc.Testing.Xunit\Microsoft.AspNetCore.Mvc.Testing.Xunit.csproj", "{5248D809-E5E5-49FE-B3E8-428D454C63B3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -756,6 +760,30 @@ Global
|
|||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -818,5 +846,10 @@ Global
|
|||
{0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{5248D809-E5E5-49FE-B3E8-428D454C63B3} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
// 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.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Xunit.Internal
|
||||
{
|
||||
internal class CultureReplacer : IDisposable
|
||||
{
|
||||
private const string _defaultCultureName = "en-GB";
|
||||
private const string _defaultUICultureName = "en-US";
|
||||
private static readonly CultureInfo _defaultCulture = new CultureInfo(_defaultCultureName);
|
||||
private readonly CultureInfo _originalCulture;
|
||||
private readonly CultureInfo _originalUICulture;
|
||||
private readonly long _threadId;
|
||||
|
||||
// Culture => Formatting of dates/times/money/etc, defaults to en-GB because en-US is the same as InvariantCulture
|
||||
// We want to be able to find issues where the InvariantCulture is used, but a specific culture should be.
|
||||
//
|
||||
// UICulture => Language
|
||||
public CultureReplacer(string culture = _defaultCultureName, string uiCulture = _defaultUICultureName)
|
||||
: this(new CultureInfo(culture), new CultureInfo(uiCulture))
|
||||
{
|
||||
}
|
||||
|
||||
public CultureReplacer(CultureInfo culture, CultureInfo uiCulture)
|
||||
{
|
||||
_originalCulture = CultureInfo.CurrentCulture;
|
||||
_originalUICulture = CultureInfo.CurrentUICulture;
|
||||
_threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
CultureInfo.CurrentUICulture = uiCulture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the culture that is used as the default value for CultureInfo.DefaultThreadCurrentCulture when CultureReplacer is used.
|
||||
/// </summary>
|
||||
public static string DefaultCultureName
|
||||
{
|
||||
get { return _defaultCultureName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentUICulture when CultureReplacer is used.
|
||||
/// </summary>
|
||||
public static string DefaultUICultureName
|
||||
{
|
||||
get { return _defaultUICultureName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentCulture when CultureReplacer is used.
|
||||
/// </summary>
|
||||
public static CultureInfo DefaultCulture
|
||||
{
|
||||
get { return _defaultCulture; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if(Thread.CurrentThread.ManagedThreadId != _threadId)
|
||||
{
|
||||
throw new InvalidOperationException("The current thread is not the same as the thread " +
|
||||
"invoking the constructor. This should never happen.");
|
||||
}
|
||||
|
||||
CultureInfo.CurrentCulture = _originalCulture;
|
||||
CultureInfo.CurrentUICulture = _originalUICulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Xunit.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A middleware that ensures web sites run in a consistent culture. Currently useful for tests that format dates,
|
||||
/// times, or numbers. Will be more useful when we have localized resources.
|
||||
/// </summary>
|
||||
public class CultureReplacerMiddleware
|
||||
{
|
||||
// Have no current need to use cultures other than the ReplaceCultureAttribute defaults (en-GB, en-US).
|
||||
private readonly ReplaceCultureAttribute _replaceCulture = new ReplaceCultureAttribute();
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public CultureReplacerMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
// Use ReplaceCultureAttribute to avoid thread consistency checks in CultureReplacer. await doesn't
|
||||
// necessarily end on the original thread. For this case, problems arise when next middleware throws. Can
|
||||
// remove the thread consistency checks once culture is (at least for .NET 4.6) handled using
|
||||
// AsyncLocal<CultureInfo>.
|
||||
try
|
||||
{
|
||||
_replaceCulture.Before(methodUnderTest: null);
|
||||
await _next(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_replaceCulture.After(methodUnderTest: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Xunit.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts the <see cref="CultureReplacerMiddleware"/> at the beginning of the pipeline.
|
||||
/// </summary>
|
||||
public class CultureReplacerStartupFilter : IStartupFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
||||
{
|
||||
return AddCulture;
|
||||
|
||||
void AddCulture(IApplicationBuilder builder)
|
||||
{
|
||||
builder.UseMiddleware<CultureReplacerMiddleware>();
|
||||
next(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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.Globalization;
|
||||
using System.Reflection;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Xunit.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces the current culture and UI culture for the test.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
internal class ReplaceCultureAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
private const string _defaultCultureName = "en-GB";
|
||||
private const string _defaultUICultureName = "en-US";
|
||||
private CultureInfo _originalCulture;
|
||||
private CultureInfo _originalUICulture;
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the current culture and UI culture to en-GB and en-US respectively.
|
||||
/// </summary>
|
||||
public ReplaceCultureAttribute() :
|
||||
this(_defaultCultureName, _defaultUICultureName)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the current culture and UI culture based on specified values.
|
||||
/// </summary>
|
||||
public ReplaceCultureAttribute(string currentCulture, string currentUICulture)
|
||||
{
|
||||
Culture = new CultureInfo(currentCulture);
|
||||
UICulture = new CultureInfo(currentUICulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CultureInfo.CurrentCulture"/> for the test. Defaults to en-GB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We
|
||||
/// want to be able to find bugs where we're accidentally relying on the Invariant instead of the
|
||||
/// user's culture.
|
||||
/// </remarks>
|
||||
public CultureInfo Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CultureInfo.CurrentUICulture"/> for the test. Defaults to en-US.
|
||||
/// </summary>
|
||||
public CultureInfo UICulture { get; }
|
||||
|
||||
public override void Before(MethodInfo methodUnderTest)
|
||||
{
|
||||
_originalCulture = CultureInfo.CurrentCulture;
|
||||
_originalUICulture = CultureInfo.CurrentUICulture;
|
||||
|
||||
CultureInfo.CurrentCulture = Culture;
|
||||
CultureInfo.CurrentUICulture = UICulture;
|
||||
}
|
||||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
CultureInfo.CurrentCulture = _originalCulture;
|
||||
CultureInfo.CurrentUICulture = _originalUICulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>XUnit convenience fixture for creating functional tests for MVC applications.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<PackageTags>aspnetcore;aspnetcoremvc;aspnetcoremvctesting</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="build\**\*.targets" Pack="true" PackagePath="%(Identity)" />
|
||||
<Content Include="build\xunit.runner.json" Pack="true" PackagePath="%(Identity)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.Mvc.Testing.Xunit.Internal;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
/// <summary>
|
||||
/// XUnit 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;
|
||||
|
||||
public WebApplicationTestFixture()
|
||||
: this("src")
|
||||
{
|
||||
}
|
||||
|
||||
protected WebApplicationTestFixture(string solutionRelativePath)
|
||||
: this("*.sln", solutionRelativePath)
|
||||
{
|
||||
}
|
||||
|
||||
protected WebApplicationTestFixture(string solutionSearchPattern, string solutionRelativePath)
|
||||
{
|
||||
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
|
||||
|
||||
// This step assumes project name = assembly name.
|
||||
var projectName = startupAssembly.GetName().Name;
|
||||
var projectPath = Path.Combine(solutionRelativePath, projectName);
|
||||
var builder = new MvcWebApplicationBuilder<TStartup>()
|
||||
.UseSolutionRelativeContentRoot(projectPath)
|
||||
.UseApplicationAssemblies();
|
||||
|
||||
ConfigureApplication(builder);
|
||||
|
||||
var xunitRunnerJson = new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), "xunit.runner.json"));
|
||||
if (!xunitRunnerJson.Exists)
|
||||
{
|
||||
Console.WriteLine("Can't find xunit.runner.json. " +
|
||||
"Functional tests require '\"shadowCopy\": false' to work properly. " +
|
||||
"Make sure your XUnit configuration has that setup.");
|
||||
}
|
||||
|
||||
var content = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(xunitRunnerJson.FullName));
|
||||
if (!content.TryGetValue("shadowCopy", out var token) || !(bool)token)
|
||||
{
|
||||
Console.WriteLine("'shadowCopy' is not set to true on xunit.runner.json. " +
|
||||
"Functional tests require '\"shadowCopy\": false' to work properly. " +
|
||||
"Make sure your XUnit configuration has that setup.");
|
||||
}
|
||||
|
||||
using (new CultureReplacer())
|
||||
{
|
||||
_server = builder.Build();
|
||||
}
|
||||
|
||||
Client = _server.CreateClient();
|
||||
Client.BaseAddress = new Uri("http://localhost");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives a fixture an opportunity to configure the application before it gets built.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="MvcWebApplicationBuilder{TStartup}"/> for the application.</param>
|
||||
protected virtual void ConfigureApplication(MvcWebApplicationBuilder<TStartup> builder)
|
||||
{
|
||||
builder.ConfigureAfterStartup(s => s.TryAddEnumerable(ServiceDescriptor.Transient<IStartupFilter, CultureReplacerStartupFilter>()));
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
public HttpClient CreateClient()
|
||||
{
|
||||
var client = _server.CreateClient();
|
||||
client.BaseAddress = new Uri("http://localhost");
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public HttpClient CreateClient(Uri baseAddress, params DelegatingHandler[] handlers)
|
||||
{
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Client.Dispose();
|
||||
_server.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<Target Name="CopyXunitRunner" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
|
||||
<ItemGroup>
|
||||
<XunitRunnerJson Include="$(MSBuildThisFileDirectory)xunit.runner.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ShouldCopyXUnitRunnerJson Condition="Exists('%(XunitRunnerJson.FullPath)') and !Exists('$(OutputPath)xunit.runner.json') and '$(DisableCopyXunitRunnerJson)' == ''" >true</ShouldCopyXUnitRunnerJson>
|
||||
</PropertyGroup>
|
||||
<Copy SourceFiles="%(XunitRunnerJson.FullPath)" DestinationFolder="$(OutputPath)" Condition="'$(ShouldCopyXUnitRunnerJson)' != ''" />
|
||||
<Message Condition="'$(ShouldCopyXUnitRunnerJson)' != ''" Text="Automatically copied 'xunit.runner.json' to '$(OutputPath)'. Set a non-empty value to 'DisableCopyXunitRunnerJson' to disable this behavior. You will have to manually configure 'shadowCopy: false' for functional tests to run properly." />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegating handler for managing cookies on functional tests.
|
||||
/// </summary>
|
||||
public class CookieContainerHandler : DelegatingHandler
|
||||
{
|
||||
public CookieContainerHandler(HttpMessageHandler innerHandler)
|
||||
: base(innerHandler)
|
||||
{
|
||||
}
|
||||
|
||||
public CookieContainer Container { get; } = new CookieContainer();
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var cookieHeader = Container.GetCookieHeader(request.RequestUri);
|
||||
request.Headers.Add("Cookie", cookieHeader);
|
||||
|
||||
var response = await base.SendAsync(request, cancellationToken);
|
||||
|
||||
if (response.Headers.TryGetValues("Set-Cookie", out var setCookieHeaders))
|
||||
{
|
||||
foreach (var header in setCookieHeaders)
|
||||
{
|
||||
Container.SetCookies(response.RequestMessage.RequestUri, header);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to orchestrate service registrations in <see cref="TestStartup{TStartup}"/>.
|
||||
/// </summary>
|
||||
public class TestServiceRegistrations
|
||||
{
|
||||
public IList<Action<IServiceCollection>> Before { get; set; } = new List<Action<IServiceCollection>>();
|
||||
public IList<Action<IServiceCollection>> After { get; set; } = new List<Action<IServiceCollection>>();
|
||||
|
||||
public void ConfigureServices(IServiceCollection services, Action startupConfigureServices)
|
||||
{
|
||||
foreach (var config in Before)
|
||||
{
|
||||
config(services);
|
||||
}
|
||||
|
||||
startupConfigureServices();
|
||||
|
||||
foreach (var config in After)
|
||||
{
|
||||
config(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Testing.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Fake startup class used in functional tests to decorate the registration of
|
||||
/// ConfigureServices.
|
||||
/// </summary>
|
||||
/// <typeparam name="TStartup">The startup class of your application.</typeparam>
|
||||
public class TestStartup<TStartup> where TStartup : class
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly TestServiceRegistrations _registrations;
|
||||
private readonly TStartup _instance;
|
||||
|
||||
public TestStartup(IServiceProvider serviceProvider, TestServiceRegistrations registrations)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_registrations = registrations;
|
||||
_instance = (TStartup)ActivatorUtilities.CreateInstance(serviceProvider, typeof(TStartup));
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var configureServices = _instance.GetType().GetMethod(nameof(ConfigureServices));
|
||||
var parameters = Enumerable.Repeat(services, 1)
|
||||
.Concat(configureServices
|
||||
.GetParameters()
|
||||
.Skip(1)
|
||||
.Select(p => ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, p.ParameterType)))
|
||||
.ToArray();
|
||||
|
||||
_registrations.ConfigureServices(services, () => configureServices.Invoke(_instance, parameters));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
var configure = _instance.GetType().GetMethod(nameof(Configure));
|
||||
var parameters = Enumerable.Repeat(applicationBuilder, 1)
|
||||
.Concat(configure
|
||||
.GetParameters()
|
||||
.Skip(1)
|
||||
.Select(p => ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, p.ParameterType)))
|
||||
.ToArray();
|
||||
|
||||
configure.Invoke(_instance, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Support for writing functional tests for MVC applications.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<PackageTags>aspnetcore;aspnetcoremvc;aspnetcoremvctesting</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="build\**\*.targets" Pack="true" PackagePath="%(Identity)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// 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 <see cref="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 <see cref="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(Directory.GetCurrentDirectory(), depsFileName));
|
||||
if (!depsFile.Exists)
|
||||
{
|
||||
throw new InvalidOperationException($"Can't find'{depsFile}'. 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>'.");
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!--
|
||||
Work around https://github.com/NuGet/Home/issues/4412. MVC uses DependencyContext.Load() which looks next to a .dll
|
||||
for a .deps.json. Information isn't available elsewhere. Need the .deps.json file for all web site applications.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
The functional tests act as the host application for all test websites. Since the CLI copies all reference
|
||||
assembly dependencies in websites to their corresponding bin/{config}/refs folder we need to re-calculate
|
||||
reference assemblies for this project so there's a corresponding refs folder in our output. Without it
|
||||
our websites deps files will fail to find their assembly referenes.
|
||||
-->
|
||||
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' != 'netcoreapp2.0' ">
|
||||
<!-- Work around https://github.com/dotnet/sdk/issues/926. Align with bitness of the web site projects. -->
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
|
||||
<!--
|
||||
Work around https://github.com/Microsoft/vstest/issues/428 aka https://github.com/aspnet/Mvc/issues/5873.
|
||||
Create the appropriate binding redirects.
|
||||
-->
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CopyAditionalFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
|
||||
<ItemGroup>
|
||||
<DepsFilePaths Include="$([System.IO.Path]::ChangeExtension('%(_ResolvedProjectReferencePaths.FullPath)', '.deps.json'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutputPath)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\build\common.props" />
|
||||
<Import Project="..\..\src\Microsoft.AspNetCore.Mvc.Testing\build\Microsoft.AspNetCore.Mvc.Testing.targets" />
|
||||
<Import Project="..\..\src\Microsoft.AspNetCore.Mvc.Testing.Xunit\build\Microsoft.AspNetCore.Mvc.Testing.Xunit.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
|
|
@ -11,39 +13,15 @@
|
|||
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);FUNCTIONAL_TESTS</DefineConstants>
|
||||
|
||||
<!--
|
||||
The functional tests act as the host application for all test websites. Since the CLI copies all reference
|
||||
assembly dependencies in websites to their corresponding bin/{config}/refs folder we need to re-calculate
|
||||
reference assemblies for this project so there's a corresponding refs folder in our output. Without it
|
||||
our websites deps files will fail to find their assembly referenes.
|
||||
-->
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' != 'netcoreapp2.0' ">
|
||||
<!-- Work around https://github.com/dotnet/sdk/issues/926. Align with bitness of the web site projects. -->
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
|
||||
<!--
|
||||
Work around https://github.com/Microsoft/vstest/issues/428 aka https://github.com/aspnet/Mvc/issues/5873.
|
||||
Create the appropriate binding redirects.
|
||||
-->
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\XmlAssert.cs" />
|
||||
<EmbeddedResource Include="compiler\resources\**\*" />
|
||||
|
||||
<!--
|
||||
Work around https://github.com/Microsoft/vstest/issues/196. Execute tests with assemblies in the bin folder, not
|
||||
a temporary location. Unable to find the web site project folders otherwise.
|
||||
-->
|
||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Testing.Xunit\Microsoft.AspNetCore.Mvc.Testing.Xunit.csproj" />
|
||||
<ProjectReference Include="..\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.csproj" />
|
||||
<ProjectReference Include="..\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj" />
|
||||
<ProjectReference Include="..\WebSites\BasicWebSite\BasicWebSite.csproj" />
|
||||
|
|
@ -78,15 +56,4 @@
|
|||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Work around https://github.com/NuGet/Home/issues/4412. MVC uses DependencyContext.Load() which looks next to a .dll
|
||||
for a .deps.json. Information isn't available elsewhere. Need the .deps.json file for all web site applications.
|
||||
-->
|
||||
<Target Name="CopyDepsFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
|
||||
<ItemGroup>
|
||||
<DepsFilePaths Include="$([System.IO.Path]::ChangeExtension('%(_ResolvedProjectReferencePaths.FullPath)', '.deps.json'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutputPath)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,24 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class MvcEncodedTestFixture<TStartup> : MvcTestFixture<TStartup>
|
||||
where TStartup : class
|
||||
{
|
||||
protected override void InitializeServices(IServiceCollection services)
|
||||
protected override void ConfigureApplication(MvcWebApplicationBuilder<TStartup> builder)
|
||||
{
|
||||
base.InitializeServices(services);
|
||||
services.AddTransient<HtmlEncoder, HtmlTestEncoder>();
|
||||
services.AddTransient<JavaScriptEncoder, JavaScriptTestEncoder>();
|
||||
services.AddTransient<UrlEncoder, UrlTestEncoder>();
|
||||
base.ConfigureApplication(builder);
|
||||
builder.ConfigureBeforeStartup(services =>
|
||||
{
|
||||
services.TryAddTransient<HtmlEncoder, HtmlTestEncoder>();
|
||||
services.TryAddTransient<JavaScriptEncoder, JavaScriptTestEncoder>();
|
||||
services.TryAddTransient<UrlEncoder, UrlTestEncoder>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class MvcSampleFixture<TStartup> : MvcTestFixture<TStartup>
|
||||
where TStartup : class
|
||||
{
|
||||
public MvcSampleFixture()
|
||||
: base("samples")
|
||||
{
|
||||
}
|
||||
public MvcSampleFixture() : base("samples") { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,71 +1,30 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class MvcTestFixture<TStartup> : IDisposable
|
||||
public class MvcTestFixture<TStartup> : WebApplicationTestFixture<TStartup>
|
||||
where TStartup : class
|
||||
{
|
||||
private readonly TestServer _server;
|
||||
|
||||
public MvcTestFixture()
|
||||
: this(Path.Combine("test", "WebSites"))
|
||||
: base(Path.Combine("test", "WebSites"))
|
||||
{
|
||||
}
|
||||
|
||||
protected MvcTestFixture(string solutionRelativePath)
|
||||
: base(solutionRelativePath)
|
||||
{
|
||||
// RequestLocalizationOptions saves the current culture when constructed, potentially changing response
|
||||
// localization i.e. RequestLocalizationMiddleware behavior. Ensure the saved culture
|
||||
// (DefaultRequestCulture) is consistent regardless of system configuration or personal preferences.
|
||||
using (new CultureReplacer())
|
||||
{
|
||||
var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
|
||||
var contentRoot = SolutionPathUtility.GetProjectPath(solutionRelativePath, startupAssembly);
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.UseContentRoot(contentRoot)
|
||||
.ConfigureServices(InitializeServices)
|
||||
.UseStartup(typeof(TStartup));
|
||||
|
||||
_server = new TestServer(builder);
|
||||
}
|
||||
|
||||
Client = _server.CreateClient();
|
||||
Client.BaseAddress = new Uri("http://localhost");
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
public void Dispose()
|
||||
protected override void ConfigureApplication(MvcWebApplicationBuilder<TStartup> builder)
|
||||
{
|
||||
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 that 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);
|
||||
base.ConfigureApplication(builder);
|
||||
builder.ApplicationAssemblies.Clear();
|
||||
builder.ApplicationAssemblies.Add(typeof(TStartup).GetTypeInfo().Assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue