gpt4 book ai didi

asp.net-core - 使用Moq进行ASP.NET核心集成测试和模拟

转载 作者:行者123 更新时间:2023-12-03 16:46:13 28 4
gpt4 key购买 nike

我有以下使用自定义WebApplicationFactoryASP.NET Core integration test

public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}

public ApplicationOptions ApplicationOptions { get; private set; }

public Mock<IClockService> ClockServiceMock { get; private set; }

public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);

protected override TestServer CreateServer(IWebHostBuilder builder)
{
this.ClockServiceMock = new Mock<IClockService>(MockBehavior.Strict);

builder
.UseEnvironment("Testing")
.ConfigureTestServices(
services =>
{
services.AddSingleton(this.ClockServiceMock.Object);
});

var testServer = base.CreateServer(builder);

using (var serviceScope = testServer.Host.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
}

return testServer;
}
}

看起来应该可以,但是问题是从未调用 ConfigureTestServices方法,所以我的模拟程序从未在IoC容器中注册。您可以找到完整的源代码 here

public class FooControllerTest : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable
{
private readonly HttpClient client;
private readonly CustomWebApplicationFactory<Startup> factory;
private readonly Mock<IClockService> clockServiceMock;

public FooControllerTest(CustomWebApplicationFactory<Startup> factory)
{
this.factory = factory;
this.client = factory.CreateClient();
this.clockServiceMock = this.factory.ClockServiceMock;
}

[Fact]
public async Task Delete_FooFound_Returns204NoContent()
{
this.clockServiceMock.SetupGet(x => x.UtcNow).ReturnsAsync(new DateTimeOffset.UtcNow);

var response = await this.client.DeleteAsync("/foo/1");

Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}

public void Dispose() => this.factory.VerifyAllMocks();
}

最佳答案

我已经写了有关ASP.NET Core Integration Testing & Mocking using Moq的博客。这并不简单,需要进行一些设置,但我希望它能对某人有所帮助。这是使用ASP.NET Core 3.1所需的基本代码:
启动
您的应用程序ConfigureServices类中的ConfigureStartup方法必须是虚拟的。这样一来,我们就可以在测试中继承此类,并用模拟版本替换某些服务的生产版本。

public class Startup
{
private readonly IConfiguration configuration;
private readonly IWebHostingEnvironment webHostingEnvironment;

public Startup(IConfiguration configuration, IWebHostingEnvironment webHostingEnvironment)
{
this.configuration = configuration;
this.webHostingEnvironment = webHostingEnvironment;
}

public virtual void ConfigureServices(IServiceCollection services) =>
...

public virtual void Configure(IApplicationBuilder application) =>
...
}
测试启动
在您的测试项目中,使用在IoC中注册模拟对象和模拟对象的类来覆盖Startup类。
public class TestStartup : Startup
{
private readonly Mock<IClockService> clockServiceMock;

public TestStartup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
: base(configuration, hostingEnvironment)
{
this.clockServiceMock = new Mock<IClockService>(MockBehavior.Strict);
}

public override void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton(this.clockServiceMock);

base.ConfigureServices(services);

services
.AddSingleton(this.clockServiceMock.Object);
}
}
CustomWebApplicationFactory
在您的测试项目中,编写一个自定义的 WebApplicationFactory,它配置 HttpClient并从 TestStartup解析模拟,然后将它们公开为属性,以供我们的集成测试使用。请注意,我还将环境更改为 Testing并告诉它使用 TestStartup类进行启动。
还要注意,我已经实现了 IDisposable的`Dispose方法来验证所有严格的模拟。这意味着我不需要亲自亲自验证任何模拟。当xUnit布置测试类时,所有模拟设置的验证会自动进行。
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}

public ApplicationOptions ApplicationOptions { get; private set; }

public Mock<IClockService> ClockServiceMock { get; private set; }

public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);

protected override void ConfigureClient(HttpClient client)
{
using (var serviceScope = this.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
this.ClockServiceMock = serviceProvider.GetRequiredService<Mock<IClockService>>();
}

base.ConfigureClient(client);
}

protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder
.UseEnvironment("Testing")
.UseStartup<TestStartup>();

protected override void Dispose(bool disposing)
{
if (disposing)
{
this.VerifyAllMocks();
}

base.Dispose(disposing);
}
}
整合测试
我正在使用xUnit编写测试。请注意,传递给 CustomWebApplicationFactory的通用类型是 Startup而不是 TestStartup。此通用类型用于在磁盘上查找应用程序项目的位置,而不用于启动应用程序。
我在测试中设置了一个模拟,并实现了 IDisposable来最后验证我所有测试的所有模拟,但是您可以根据需要在测试方法本身中执行此步骤。
还要注意,我并没有使用xUnit的 IClassFixture仅启动一次应用程序,因为ASP.NET Core文档指示您这样做。如果这样做,我将不得不在每个测试之间重置模拟,而且您一次只能连续运行一次集成测试。使用以下方法,每个测试都是完全隔离的,可以并行运行。这会占用更多的CPU,并且每个测试都需要更长的时间才能执行,但我认为这是值得的。
public class FooControllerTest : CustomWebApplicationFactory<Startup>
{
private readonly HttpClient client;
private readonly Mock<IClockService> clockServiceMock;

public FooControllerTest()
{
this.client = this.CreateClient();
this.clockServiceMock = this.ClockServiceMock;
}

[Fact]
public async Task GetFoo_Default_Returns200OK()
{
this.clockServiceMock.Setup(x => x.UtcNow).ReturnsAsync(new DateTimeOffset(2000, 1, 1));

var response = await this.client.GetAsync("/foo");

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
xunit.runner.json
我正在使用xUnit。我们需要关闭阴影复制,因此将任何单独的文件(例如 appsettings.json)放在应用程序DLL文件旁边的正确位置。这确保了我们在集成测试中运行的应用程序仍然可以读取 appsettings.json文件。
{
"shadowCopy": false
}
appsettings.Testing.json
如果您具有仅针对集成测试而要更改的配置,则可以在应用程序中添加 appsettings.Testing.json文件。该配置文件仅在我们的集成测试中读取,因为我们将环境名称设置为“测试”。

关于asp.net-core - 使用Moq进行ASP.NET核心集成测试和模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57577045/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com