Search code examples
c#.netmoqpollyfakeiteasy

How to mock AsyncPolicyWrap or AsyncPolicy in .Net Core using FakeItEasy


I have a code like this (I have to test a repo, you'll see the code below)

public class SomeClass
{
    public AsyncPolicyWrap PropName { get; }
    
    public SomeClass(...)
    {     
        PropName = Policy.WrapAsync(someRetry,someCircuitBreaker)
        // here there are passed some methods that return someRetry - AsyncRetryPolicy  
        // and  someCircuitBreaker - AsyncCircuitBreakerPolicy  
    }
}

then I have another repo class

public class SomeRepo : ISomeRepo
{
    private readonly AsyncPolicy _somePolicy;
    
    public SomeRepo(..., SomeClass someClass) : base(...)
    {
        _somePolicy = someClass.PropName;
    }
    public async Task<Result<SomeDTO>> GetDTO(Guid someId)
    {
        var someResponse = await _somePolicy.ExecuteAsync(() =>
            HttpClient.GetAsync(serviceName, $"endpointUrl"));
        ...
    }
}

2 pieces of code above can't be changed cause they are in prod and I as a junior dev just have to cover code with tests if possible

I have tried to write a test like this

[TestMethod] 
public async Task DoStuff()
{
    var repository = DefaultSome();

    var result = await repository.GetDTO(new Guid());

    result.ShouldNotBeNull(); // don't pay attention I'll change stuff which has to be asserted
}
private SomeRepo DefaultSome(Some some = null) 
{
    some = some ?? A.Fake<ISome>();
    /// HERE I TRIED TO MOCK STUFF IN DIFFERENT WAYS AND I HAVE AN ERROR
    var policyWrap = A.Dummy<AsyncPolicyWrap>();
    //var test = Policy.WrapAsync(A.Fake<AsyncRetryPolicy>(), A.Fake<AsyncCircuitBreakerPolicy>());
    //var test = Policy.WrapAsync(A.Fake<IAsyncPolicy>(), A.Fake<IAsyncPolicy>());

    A.CallTo(() =>
            policyWrap.ExecuteAsync(A<Func<Task<HttpResponseMessage>>>._))
        .Returns(new HttpResponseMessage(HttpStatusCode.OK));

    var policy = A.Fake<RetryPolicies>();
    A.CallTo(() =>
            policy.PropName)
        .Returns(policyWrap);

    return new SomeRepo(some, ..., policy);
}

here is an error i get The error itself

I get similar for commented // var test = ... variats


Solution

  • Concrete vs Abstract

    Whenever you need to mock something then rely on abstraction rather than concrete implementation.

    AsyncPolicyWrap is a concrete class not an abstract like AsyncPolicy

    Also as the exception says this class does not have a public parameterless constructor.
    It has an internal ctor with 2 parameters:

    internal AsyncPolicyWrap(AsyncPolicy outer, IAsyncPolicy inner)
        : base(outer.ExceptionPredicates)
    {
        _outer = outer;
        _inner = inner;
    }
    

    So, you should prefer AsyncPolicy abstract class or IAsyncPolicy interface.

    With or without result

    Please be aware that in Polly each Policy has two versions:

    • One which does not return any result
    • One which does return some result

    Based on the SomeRepo's code your Policy should return an HttpResponseMessage.

    So, you should use IAsyncPolicy<HttpResponseMessage> or AsyncPolicy<HttpResponseMessage> to indicate that your policy will return an HttpResponseMessage.

    Mocking

    Whenever you mock an IAsyncPolicy<HttpResponseMessage> then you don't have to recreate the combined policy (like you did in the comments). All you have to do is to define how should the ExecuteAsync behave.

    Happy path:

    var mockedPolicy = new Mock<IAsyncPolicy<HttpResponseMessage>>();
    mockedPolicy
        .Setup(policy => policy.ExecuteAsync(It.IsAny<Func<Task<HttpResponseMessage>>>()))
        .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
    

    Unhappy path:

    var mockedPolicy = new Mock<IAsyncPolicy<HttpResponseMessage>>();
    mockedPolicy
        .Setup(policy => policy.ExecuteAsync(It.IsAny<Func<Task<HttpResponseMessage>>>()))
        .ThrowsAsync(new HttpRequestException("Something bad happened"));
    

    I've used moq to mock the policy but the same concept can be applied for FakeItEasy.