【问题标题】:How to validate values in a mocked async method with xunit?如何使用 xunit 验证模拟异步方法中的值?
【发布时间】: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


【解决方案1】:

如果我理解正确,您是在尝试断言 BulkUpdate 的参数是您所期望的,可以这样完成...

repositoryMock.Verify(x => x.BulkUpdate(It.Is<IEnumerable<MyDocument>>(
    notifications=>notifications.All(n => n.Detail != null)), It.IsAny<bool>()));

然后类似Map。

【讨论】:

  • 有趣。我会试一试。谢谢
  • @MonkeyWrench 进展如何?
  • 我必须仔细检查我们做了什么,但我相信我们最终走上了不同的道路。最后让 mock 用测试中的数据设置一个变量,使用带有超时的信号量来验证方法是否被调用,然后验证数据。丑陋但有效。
猜你喜欢
  • 2014-06-08
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多