【发布时间】:2018-09-05 22:18:25
【问题描述】:
我希望能够在我的单元测试中验证传递给异步方法的参数。但是由于该方法是异步的,因此执行断言的任务对整个单元测试没有影响。断言的异常不会传递给单元测试框架 (xunit)。我正在模拟我的存储库方法,这些方法由 MVC IHostedService 调用。
例子:
CancellationTokenSource sourceToken = new CancellationTokenSource();
var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(x => x.BulkUpdate(It.IsAny<IEnumerable<MyDocument>>(), It.IsAny<bool>()))
.ReturnsAsync((IEnumerable<MyDocument> notifications, bool upsert) =>
{
// verify all the event Ids are in the newly created Notifications (these will actually all be NULL since we can't set the eventIds in the Event object
Assert.True(notifications.All(x => x.Detail != null));
Assert.True(notifications.All(x => x.Map != null));
sourceToken.Cancel();
return notifications.Select(x => x.Id);
});
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Task.Delay(20000, sourceToken.Token));
Assert.True(sourceToken.IsCancellationRequested);
此处的示例不包括启动 MVC 服务,但 Startup 将我的 IHostedService 创建为单例,这也会立即启动它运行。它最终会调用这个 BulkUpdate 方法,我希望能够验证 IHostedService 是否使用正确的数据调用 BulkUpdate。 CancellationTokenSource 用于表示测试可以退出,因为 IHostedService 已经完成了我要测试的工作。
后台服务如何启动: 在单元测试启动类中:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHostedService, MyBackgroundService>();
}
我们有一个生成 IWebHostBuilder 的方法...
internal static IWebHostBuilder GetWebHostBuilder(Mock<IIRepository> repositoryMock = null)
{
var environment = "Development"; //
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
var configuration = builder.Build();
var webHostBuilder = new WebHostBuilder()
.UseEnvironment(environment)
.UseConfiguration(configuration)
.ConfigureServices(services =>
{
// Add the repository mocks and the type of user thats needed for authentications in the Controller being tested
// These will be added before the IntegrationTestStartup.ConfigureTransientServices method runs and adds
// the repository objects.
if(repositoryMock != null)
services.AddTransient<IRepository>((y) => { return repositoryMock.Object; });
})
.UseStartup<UnitTestStartup>(); // This is the Startup of AHENS
return webHostBuilder;
}
我们在输出单元测试中使用注入模拟存储库来测试后台服务...
TestServer testServer = new TestServer(TestBase.GetWebHostBuilder(repositoryMock));
这一切都“有效”,除非其中一个断言失败(抛出异常)。如何让断言汇总到单元测试以使测试失败?有没有更好的方法来做到这一点?我们有测试用例,我们想要验证模拟的存储库方法不会被调用,或者被多次调用。这是一个多线程应用程序问题的单元测试。
【问题讨论】:
-
好吧,这开始感觉像是XY problem。提供一个minimal reproducible example,可以用来复现和更好地理解实际问题。就所提供的代码而言,目前没有足够的上下文。这也是 unit 测试还是 integration 测试?
-
上例中提供了问题的根本原因。主要问题是 mock 提供的 async 函数中的断言不会通过单元测试。为了简单起见,我没有在示例代码中包含所有用例。我有多次调用模拟的情况,需要根据条件进行不同的验证。如果调用它们,我有需要失败的模拟。这些情况是由注入后台 IHostedService 线程的数据设置的。这是关于单测试多线程应用程序的问题。
-
这可能只是我的挑剔,但如果使用
TestServer,那么这绝对不是单元测试而是集成测试。孤立的单元测试有太多的实现问题。 -
单元测试与集成测试的定义与问题无关。如果需要,调用测试 Fred。测试都在内存中,可以测试单个逻辑/代码,并且不依赖于其他系统(模拟存储库)。
-
我明白你的意思。
标签: asp.net-mvc unit-testing task moq xunit