Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="MinVer" Version="6.0.0" />
<PackageVersion Include="NativeLibraryLoader" Version="1.0.13" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.2" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.Hosting;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
/// <summary>
/// Extends the default implementation of the <see cref="IWebHostFixture"/> interface to be synchronous e.g., blocking where exceptions can be captured.
/// </summary>
/// <seealso cref="WebHostFixture" />
public class BlockingWebHostFixture : WebHostFixture
{
/// <summary>
/// Initializes a new instance of the <see cref="BlockingWebHostFixture"/> class.
/// </summary>
public BlockingWebHostFixture()
{
HostRunnerCallback = host => host.Run();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
/// <summary>
/// Provides extension methods for <see cref="IHostApplicationBuilder"/>.
/// </summary>
public static class HostBuilderApplicationExtensions
{
/// <summary>
/// Converts an <see cref="IHostApplicationBuilder"/> to an <see cref="IHostBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to convert.</param>
/// <returns>The <see cref="IHostBuilder"/> instance.</returns>
/// <exception cref="ArgumentException">
/// <paramref name="builder"/> is not a <see cref="WebApplicationBuilder"/>.
/// </exception>
public static IHostBuilder ToHostBuilder(this IHostApplicationBuilder builder)
{
if (builder is WebApplicationBuilder webAppBuilder) { return webAppBuilder.Host; }
throw new ArgumentException($"The provided IHostApplicationBuilder is not a {nameof(WebApplicationBuilder)}.", nameof(builder));

Check warning on line 23 in src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs#L23

Added line #L23 was not covered by tests
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static class HttpClientExtensions
/// <exception cref="ArgumentNullException">
/// <paramref name="client"/> cannot be null.
/// </exception>
/// <remarks>Designed to be used in conjunction with <see cref="WebHostTestFactory.RunAsync(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},IAspNetCoreHostFixture)"/> and <see cref="WebHostTestFactory.RunWithHostBuilderContextAsync(System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},IAspNetCoreHostFixture)"/>.</remarks>
/// <remarks>Designed to be used in conjunction with <see cref="WebHostTestFactory.RunAsync(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},IWebHostFixture)"/> and <see cref="WebHostTestFactory.RunWithHostBuilderContextAsync(System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},IWebHostFixture)"/>.</remarks>
public static async Task<HttpResponseMessage> ToHttpResponseMessageAsync(this HttpClient client, Func<HttpClient, Task<HttpResponseMessage>> responseFactory = null)
{
if (client == null) { throw new ArgumentNullException(nameof(client)); }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using Microsoft.AspNetCore.Builder;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
/// <summary>
/// Provides a way to use Microsoft Dependency Injection in unit tests (minimal style).
/// </summary>
/// <seealso cref="IMinimalHostFixture" />
/// <seealso cref="IPipelineTest" />
public interface IMinimalWebHostFixture : IMinimalHostFixture, IPipelineTest
{
/// <summary>
/// Gets or sets the delegate that configures the HTTP request pipeline.
/// </summary>
/// <value>The delegate that configures the HTTP request pipeline.</value>
Action<IApplicationBuilder> ConfigureApplicationCallback { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
/// <summary>
/// Provides a way to use Microsoft Dependency Injection in unit tests tailored for ASP.NET Core.
/// </summary>
/// <seealso cref="IHostFixture" />
public interface IAspNetCoreHostFixture : IHostFixture, IPipelineTest
/// <seealso cref="IGenericHostFixture" />
public interface IWebHostFixture : IGenericHostFixture, IPipelineTest
{
/// <summary>
/// Gets or sets the delegate that configures the HTTP request pipeline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
/// <summary>
/// Represents the members needed for ASP.NET Core (including but not limited to MVC, Razor and related) testing.
/// </summary>
/// <seealso cref="IGenericHostTest"/>
/// <seealso cref="IHostTest"/>
/// <seealso cref="IPipelineTest" />
public interface IWebHostTest : IGenericHostTest, IPipelineTest
public interface IWebHostTest : IHostTest, IPipelineTest
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal
{
internal sealed class MinimalWebHostTest : MinimalWebHostTest<IMinimalWebHostFixture>
{
private readonly Action<IApplicationBuilder> _pipelineConfigurator;
private readonly Action<IServiceCollection> _serviceConfigurator;
private readonly Action<HostBuilderContext, IApplicationBuilder> _pipelineConfiguratorWithContext;
private readonly Action<HostBuilderContext, IServiceCollection> _serviceConfiguratorWithContext;
private readonly Action<IHostApplicationBuilder> _hostConfigurator;
Comment on lines +8 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clarify naming to avoid confusion.

Having both this class and its generic base class named MinimalWebHostTest can be confusing. Consider renaming one for clarity, for example:
• Keep the generic base class as MinimalWebHostTest<TFixture>
• Rename this sealed class to ConcreteMinimalWebHostTest or MinimalWebHostTestImpl.

private HostBuilderContext _hostBuilderContext;

internal MinimalWebHostTest(Action<IServiceCollection> serviceConfigurator, Action<IApplicationBuilder> pipelineConfigurator, Action<IHostApplicationBuilder> hostConfigurator, IMinimalWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType())
{
_serviceConfigurator = serviceConfigurator;
_pipelineConfigurator = pipelineConfigurator;
_hostConfigurator = hostConfigurator;
InitializeHostFixture(hostFixture);
}

internal MinimalWebHostTest(Action<HostBuilderContext, IServiceCollection> serviceConfigurator, Action<HostBuilderContext, IApplicationBuilder> pipelineConfigurator, Action<IHostApplicationBuilder> hostConfigurator, IMinimalWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType())
{
_serviceConfiguratorWithContext = serviceConfigurator;
_pipelineConfiguratorWithContext = pipelineConfigurator;
_hostConfigurator = hostConfigurator;
InitializeHostFixture(hostFixture);
}

private void InitializeHostFixture(IMinimalWebHostFixture hostFixture)
{
if (!hostFixture.HasValidState())
{
hostFixture.ConfigureHostCallback = ConfigureHost;
hostFixture.ConfigureCallback = Configure;
hostFixture.ConfigureApplicationCallback = ConfigureApplication;
hostFixture.ConfigureHost(this);
}
Host = hostFixture.Host;
Application = hostFixture.Application;
Configure(hostFixture.Configuration, hostFixture.Environment);
}

public override void ConfigureApplication(IApplicationBuilder app)
{
_pipelineConfigurator?.Invoke(app);
_pipelineConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc =>
{
hbc.Configuration = Configuration;
hbc.HostingEnvironment = Environment;
return hbc;
}), app);
}

protected override void ConfigureHost(IHostApplicationBuilder hb)
{
_hostBuilderContext = new HostBuilderContext(hb.Properties);
_hostConfigurator?.Invoke(hb);
_serviceConfigurator?.Invoke(hb.Services);
_serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc =>
{
hbc.Configuration = hb.Configuration;
hbc.HostingEnvironment = hb.Environment;
return hbc;
}), hb.Services);
hb.Services.AddFakeHttpContextAccessor();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal
{
internal sealed class WebHostTest : WebHostTest<IWebHostFixture>
{
private readonly Action<IApplicationBuilder> _pipelineConfigurator;
private readonly Action<IServiceCollection> _serviceConfigurator;
private readonly Action<HostBuilderContext, IApplicationBuilder> _pipelineConfiguratorWithContext;
private readonly Action<HostBuilderContext, IServiceCollection> _serviceConfiguratorWithContext;
private readonly Action<IHostBuilder> _hostConfigurator;
private HostBuilderContext _hostBuilderContext;

internal WebHostTest(Action<IServiceCollection> serviceConfigurator, Action<IApplicationBuilder> pipelineConfigurator, Action<IHostBuilder> hostConfigurator, IWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType())
{
_serviceConfigurator = serviceConfigurator;
_pipelineConfigurator = pipelineConfigurator;
_hostConfigurator = hostConfigurator;
InitializeHostFixture(hostFixture);
}

internal WebHostTest(Action<HostBuilderContext, IServiceCollection> serviceConfigurator, Action<HostBuilderContext, IApplicationBuilder> pipelineConfigurator, Action<IHostBuilder> hostConfigurator, IWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType())
{
_serviceConfiguratorWithContext = serviceConfigurator;
_pipelineConfiguratorWithContext = pipelineConfigurator;
_hostConfigurator = hostConfigurator;
InitializeHostFixture(hostFixture);
}

private void InitializeHostFixture(IWebHostFixture hostFixture)
{
if (!hostFixture.HasValidState())
{
hostFixture.ConfigureHostCallback = ConfigureHost;
hostFixture.ConfigureCallback = Configure;
hostFixture.ConfigureServicesCallback = ConfigureServices;
hostFixture.ConfigureApplicationCallback = ConfigureApplication;
hostFixture.ConfigureHost(this);
}
Host = hostFixture.Host;
Application = hostFixture.Application;
Configure(hostFixture.Configuration, hostFixture.Environment);
}

public override void ConfigureApplication(IApplicationBuilder app)
{
_pipelineConfigurator?.Invoke(app);
_pipelineConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc =>
{
hbc.Configuration = Configuration;
hbc.HostingEnvironment = Environment;
return hbc;
}), app);
}

protected override void ConfigureHost(IHostBuilder hb)
{
_hostBuilderContext = new HostBuilderContext(hb.Properties);
_hostConfigurator?.Invoke(hb);
}

public override void ConfigureServices(IServiceCollection services)
{
_serviceConfigurator?.Invoke(services);
_serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc =>
{
hbc.Configuration = Configuration;
hbc.HostingEnvironment = Environment;
return hbc;
}), services);
services.AddFakeHttpContextAccessor();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
/// <summary>
/// Provides a default implementation of the <see cref="IMinimalWebHostFixture"/> interface.
/// </summary>
/// <seealso cref="MinimalHostFixture"/>
/// <seealso cref="IMinimalWebHostFixture" />
/// <remarks>This is the "modern" minimal style implementation of <see cref="WebHostFixture"/>.</remarks>
public class MinimalWebHostFixture : MinimalHostFixture, IMinimalWebHostFixture
{
/// <summary>
/// Initializes a new instance of the <see cref="MinimalWebHostFixture"/> class.
/// </summary>
public MinimalWebHostFixture()
{
}

/// <summary>
/// Creates and configures the <see cref="IHost" /> of this instance.
/// </summary>
/// <param name="hostTest">The object that inherits from <see cref="MinimalWebHostTest{T}"/>.</param>
/// <remarks><paramref name="hostTest"/> was added to support those cases where the caller is required in the host configuration.</remarks>
/// <exception cref="ArgumentNullException">
/// <paramref name="hostTest"/> is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="hostTest"/> is not assignable from <see cref="MinimalWebHostTest{T}"/>.
/// </exception>
public override void ConfigureHost(Test hostTest)
{
if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); }
if (!HasTypes(hostTest.GetType(), typeof(MinimalWebHostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(MinimalWebHostTest<>), $"{nameof(hostTest)} is not assignable from MinimalWebHostTest<T>."); }

var hb = WebApplication.CreateBuilder(new WebApplicationOptions()
{
EnvironmentName = "Development",
ApplicationName = hostTest.CallerType.Assembly.GetName().Name
});

hb.WebHost.UseTestServer(o => o.PreserveExecutionContext = true);

Configuration = hb.Configuration;
Environment = hb.Environment;

ConfigureCallback(Configuration, Environment);

ConfigureHostCallback(hb);

var webApplication = hb.Build();

ConfigureApplicationCallback(webApplication);
Application = webApplication;

Host = webApplication;

HostRunnerCallback(Host);
}

/// <summary>
/// Gets or sets the delegate that configures the HTTP request pipeline.
/// </summary>
/// <value>The delegate that configures the HTTP request pipeline.</value>
public Action<IApplicationBuilder> ConfigureApplicationCallback { get; set; }

/// <summary>
/// Gets the <see cref="IApplicationBuilder" /> initialized by the <see cref="IHost" />.
/// </summary>
/// <value>The <see cref="IApplicationBuilder" /> initialized by the <see cref="IHost" />.</value>
public IApplicationBuilder Application { get; protected set; }
}
}
Loading
Loading