【问题标题】:Getting result from Func<Task<T>>从 Func<Task<T>> 获取结果
【发布时间】:2018-02-16 05:22:27
【问题描述】:

待测方法

protected override async Task<Name> DoExecuteAsync(NameContext context)
{
    context.ThrowIfNull("context");
    var request = new Request
                      {
                          Id = context.Id,
                          Principal = context.UserPrincipal,
                      };
        return await this.repository.NameAsync(request, new CancellationToken(), context.ControllerContext.CreateLoggingContext());
    }

    protected override Name HandleError(NameContext viewContext, Exception exception)
    {
    if (this.errorSignaller != null)
    {
    this.errorSignaller.SignalFromCurrentContext(exception);
    }

    return Name.Unknown;
} 

这是实现

public abstract class BaseQueryAsync<TInput, TOutput> : IQueryAsync<TInput, TOutput>
{
    public async Task<TOutput> ExecuteAsync(TInput context)
    {
        try
        {
            return await this.DoExecuteAsync(context);
        }
        catch (Exception e)
        {
            return this.HandleError(context, e);
        }
    }

    protected abstract Task<TOutput> DoExecuteAsync(TInput context);    

    protected virtual TOutput HandleError(TInput viewContext, Exception exception)
    {    
        ExceptionDispatchInfo.Capture(exception).Throw();
    }
}

测试用例如下所示

[SetUp]
public void Setup()
{
    var httpContext = MvcMockHelpers.MockHttpContext(isAuthenticated: true);
        this.controller = new Mock<Controller>();
    this.controller.Object.SetMockControllerContext(httpContext.Object);
    this.repoMock = new Mock<IRepository>();
    this.errorSignaller = new Mock<IErrorSignaller>();
    this.query = new NameQuery(this.repoMock.Object, this.errorSignaller.Object);
    this.userPrinciple = new Mock<IPrincipal>();
    this.context = new NameContext(this.controller.Object.ControllerContext, this.userPrinciple.Object);
}

[Test]
public async Task TestDoExecuteAsyncWhenRepositoryFails()
{
    // Arrange
    this.repoMock.Setup(
    x => x.NameAsync(
    It.IsAny<Request>(),
    It.IsAny<CancellationToken>(),
    It.IsAny<ILoggingContext>())).Throws<Exception>();

    // Act
    Func<Task<Name>> act = async () => await this.query.ExecuteAsync(this.context);

    // Assert
    act.ShouldNotThrow();
    this.errorSignaller.Verify(s => s.SignalFromCurrentContext(It.IsAny<Exception>()), Times.Once);
}

要验证名称对象,当我在行前使用var result = await act()

this.errorSignaller.Verify(s => s.SignalFromCurrentContext(It.IsAny<Exception>()), Times.Once);

this.errorSignaller.Verify 失败,因为它的计数是 2 而不是 1。我的目的是检查 Name 对象以及以下代码。

act.ShouldNotThrow();
this.errorSignaller.Verify(s => s.SignalFromCurrentContext(It.IsAny<Exception>()), Times.Once);

我知道如果我写一个新的测试用例,我可以很容易地验证它,但是有什么方法我可以在这个测试中完全做到吗?

【问题讨论】:

  • 它是一个Func,即一个函数。你调用它来得到结果。由于是异步函数,所以你使用await,即var name=await act();
  • 已经在调用await this.query.ExecuteAsync(this.context);,这就是为什么我能够验证act.ShouldNotThrow();我不想再调用它了,我的理解对吗?
  • @user3910075 你的理解有误。关于调用该函数的评论是准确的。您可能需要提供更多有关您正在尝试做的事情的背景信息。显示单元测试的minimal reproducible example。这似乎是XY problem。您要达到的最终目标是什么?

标签: c# unit-testing async-await nunit fluent-assertions


【解决方案1】:

如果要测试结果,请使用:

名称结果 = 等待 this.query.ExecuteAsync(this.context);

result.Should().Be(expectefResult);

确保你的测试方法公开异步任务

【讨论】:

    【解决方案2】:

    更新

    为了能够验证名称,您需要在函数中设置它。

    //...code removed for brevity
    Name expectedName = Name.Unknown;
    Name actualName = null;
    
    // Act
    Func<Task> act = async () => {
        actualName = await this.query.ExecuteAsync(this.context);
    };
    
    // Assert
    act.ShouldNotThrow();
    actualName
        .Should().NotBeNull()
        .And.Be(expectedName);
    //...rest of code
    

    原创

    正如 cmets 中已经提到的,act 是一个返回 Task 的函数。

    在等待其实现时,仍需要调用该函数本身。而且由于该函数返回一个任务,因此也需要等待。

    Func<Task<Name>> act = async () => await this.query.ExecuteAsync(this.context);
    var name = await act();
    

    与具有以下功能相同。

    async Task<Name> act() {
        return await this.query.ExecuteAsync(this.context);
    }
    

    你必须以同样的方式等待它

    var name = await act();
    

    唯一的区别是前一个示例在委托中具有该功能。

    尽量避免将 .Result 之类的阻塞调用与 async/await 代码混用。这往往会导致死锁。

    【讨论】:

      【解决方案3】:

      你可以试试看

      await query.ExecuteAsync(this.context);
      

      this.query.ExecuteAsync(this.context).GetAwaiter().GetResult();
      

      如果是 Func:

      act.Invoke().GetAwaiter().GetResult();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        相关资源
        最近更新 更多