[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
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26228.9
|
VisualStudioVersion = 15.0.26615.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -122,6 +122,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesWebSite", "test\W
|
||||||
EndProject
|
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}"
|
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
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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|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.ActiveCfg = Release|Any CPU
|
||||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|x86.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -818,5 +846,10 @@ Global
|
||||||
{0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
{0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
{4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
{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
|
EndGlobalSection
|
||||||
EndGlobal
|
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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\..\build\common.props" />
|
<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>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||||
|
|
@ -11,39 +13,15 @@
|
||||||
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES</DefineConstants>
|
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES</DefineConstants>
|
||||||
<DefineConstants>$(DefineConstants);FUNCTIONAL_TESTS</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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\XmlAssert.cs" />
|
<Compile Include="..\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\XmlAssert.cs" />
|
||||||
<EmbeddedResource Include="compiler\resources\**\*" />
|
<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>
|
||||||
|
|
||||||
<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\ApiExplorerWebSite\ApiExplorerWebSite.csproj" />
|
||||||
<ProjectReference Include="..\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj" />
|
<ProjectReference Include="..\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj" />
|
||||||
<ProjectReference Include="..\WebSites\BasicWebSite\BasicWebSite.csproj" />
|
<ProjectReference Include="..\WebSites\BasicWebSite\BasicWebSite.csproj" />
|
||||||
|
|
@ -78,15 +56,4 @@
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,24 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.WebEncoders.Testing;
|
using Microsoft.Extensions.WebEncoders.Testing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
public class MvcEncodedTestFixture<TStartup> : MvcTestFixture<TStartup>
|
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);
|
base.ConfigureApplication(builder);
|
||||||
services.AddTransient<HtmlEncoder, HtmlTestEncoder>();
|
builder.ConfigureBeforeStartup(services =>
|
||||||
services.AddTransient<JavaScriptEncoder, JavaScriptTestEncoder>();
|
{
|
||||||
services.AddTransient<UrlEncoder, UrlTestEncoder>();
|
services.TryAddTransient<HtmlEncoder, HtmlTestEncoder>();
|
||||||
|
services.TryAddTransient<JavaScriptEncoder, JavaScriptTestEncoder>();
|
||||||
|
services.TryAddTransient<UrlEncoder, UrlTestEncoder>();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
public class MvcSampleFixture<TStartup> : MvcTestFixture<TStartup>
|
public class MvcSampleFixture<TStartup> : MvcTestFixture<TStartup>
|
||||||
|
where TStartup : class
|
||||||
{
|
{
|
||||||
public MvcSampleFixture()
|
public MvcSampleFixture() : base("samples") { }
|
||||||
: base("samples")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,30 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.IO;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
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()
|
public MvcTestFixture()
|
||||||
: this(Path.Combine("test", "WebSites"))
|
: base(Path.Combine("test", "WebSites"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MvcTestFixture(string solutionRelativePath)
|
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; }
|
protected override void ConfigureApplication(MvcWebApplicationBuilder<TStartup> builder)
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
Client.Dispose();
|
base.ConfigureApplication(builder);
|
||||||
_server.Dispose();
|
builder.ApplicationAssemblies.Clear();
|
||||||
}
|
builder.ApplicationAssemblies.Add(typeof(TStartup).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue