Getting result from Func<Task<T>>

2.4k views Asked by At

Method Under Test

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;
} 

This is implementation of

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();
    }
}

Test Case goes like below

[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);
}

To verify the Name Object ,When I use the var result = await act() before the line

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

The this.errorSignaller.Verify fails since it's count is 2 instead of 1. My intention is to check the Name object along with below code.

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

I knew that if I write a new test case I can easily verify it, but is there any way I can do altogether in this test?

3

There are 3 answers

0
Nkosi On BEST ANSWER

Update

To be able to verify name you would need to set it in the function.

//...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

Original

As already mentioned in the comments, act is a function that returns a Task.

While its implementation is awaited, the function itself still needs to be invoked. And since the function returns a Task it too would need to be awaited.

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

It is the same as having the following function.

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

You would have to await it the same way

var name = await act();

The only difference being that the former example has the function in a delegate.

Try to avoid mixing blocking calls like .Result with async/await code. This tends to cause deadlocks.

0
Boris Modylevsky On

If you want to test the result then use:

Name result = await this.query.ExecuteAsync(this.context);

result.Should().Be(expectefResult);

Make sure to make your test method public async Task

0
Yevhen Krupin On

You can try to check it with

await query.ExecuteAsync(this.context);

or

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

and in case of Func:

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