从 Func<Task<T>> 获取结果

Posted

技术标签:

【中文标题】从 Func<Task<T>> 获取结果【英文标题】:Getting result from 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。您要达到的最终目标是什么? 【参考方案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();

【讨论】:

以上是关于从 Func<Task<T>> 获取结果的主要内容,如果未能解决你的问题,请参考以下文章

Task<IList<>> 作为 Func<> 的结果

从 Expression<Func<T, bool>> 转换为字符串

AndAlso 在几个 Expression<Func<T, bool>> 之间:从范围引用

将 Func 委托与 Async 方法一起使用

将 .net Func<T> 转换为 .net Expression<Func<T>>

泛型的使用