Search code examples
c#functional-programmingmoqxunit

In C#, what is wrong with my negative test of TryAsync?


I have the following method:

public TryAsync<bool> TryRelay(
    MontageUploadConfig montageData,
    File sourceFile,
    CancellationToken cancellationToken
) => new(async () =>
{
    byte[] fileContent = await _httpClient.GetByteArrayAsync(sourceFile.Url, cancellationToken);
    return await _attachmentController.TryUploadAttachment(montageData.EventId, fileContent, sourceFile.Name);
});

I created a couple tests to prove it works as expected. My test for a negative case is failing.

This is the test:

[Fact]
public static async Task TestRelayShouldCatchErrorsGettingFile()
{
    // Arrange
    Mock<IAttachmentControllerV6> mockAttachmentController = new();
    Mock<HttpMessageHandler> mockHttpMessageHandler = new();
    MontageUploadTaskProcessor mockProcessorUnderTest = CreateProcessor(mockAttachmentController, mockHttpMessageHandler);

    MontageUploadConfig montageData = new()
    {
        EventId = "Test001"
    };
    File sourceFile = new()
    {
        Name = "Test.pdf",
        Url = "https://www.example.com/test.pdf"
    };
    CancellationToken cancellationToken = default;

    const string message = "Expected Exception";
    mockHttpMessageHandler.SetupAnyRequest()
        .Throws(new SalesforceCacheException(message));

    // Act
    Result<bool> result = await mockProcessorUnderTest.TryRelay(montageData, sourceFile, cancellationToken)();

    // Assert
    Assert.True(result.IsFaulted);
    result.IfFail(exception =>
    {
        Assert.True(exception is Exception);
        Assert.Equal(message, exception.Message);
    });
}

This is the error:

WorkflowTests.TaskProcessor.OldOrg.MontageUploadTaskProcessorUnitTests.TestRelayShouldCatchErrorsGettingFile Source: MontageUploadTaskProcessorUnitTests.cs line 59 Duration: 144 ms

Message: SFCacheController.SalesforceCacheException : Expected Exception

Stack Trace: ThrowException.Execute(Invocation invocation) line 22 MethodCall.ExecuteCore(Invocation invocation) line 97 Setup.Execute(Invocation invocation) line 85 FindAndExecuteMatchingSetup.Handle(Invocation invocation, Mock mock) line 107 IInterceptor.Intercept(Invocation invocation) line 17 Interceptor.Intercept(IInvocation underlying) line 107 AbstractInvocation.Proceed() HttpMessageHandlerProxy.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken) HttpClient.GetByteArrayAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) <b__0>d.MoveNext() line 140 --- End of stack trace from previous location --- MontageUploadTaskProcessorUnitTests.TestRelayShouldCatchErrorsGettingFile() line 81 --- End of stack trace from previous location ---

The exception seems to be thrown after TryRelay() is invoked, but even before any assertions are attempted.

Am I wrong to expect TryAsync will catch and box the exception? Am I wrong to expect this to work in a test context? What do I need to do to make this test pass?


Solution

  • You're directly invoking the TryAsync, it's just a function, and so it won't catch anything unless you use the extensions on TryAsync, like Match

    What you could do is build a helper extension for your unit tests:

    public static Task<bool> HasFailed<E, A>(this TryAsync<A> ma) where E : Exception =>
        ma.Match(Succ: _ => false,
                 Fail: e => e is E);
    

    Then you can write:

    TryAsync<X> ma = ...;
    
    Assert.True(await ma.HasFailed<IOException, X>());