从 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>> 获取结果的主要内容,如果未能解决你的问题,请参考以下文章
从 Expression<Func<T, bool>> 转换为字符串
AndAlso 在几个 Expression<Func<T, bool>> 之间:从范围引用