Search code examples
c#unit-testingmoqdapr

verify Dapr service invocation in a unit test


How can I write a unit test that verifies a Dapr service was invoked (Service invocation) when I'm using the InvokeMethodAsync method that takes in the HttpMethod, appId, methodName, TRequest data and CancellationToken (the one used in this sample?)

usage:

await _daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(
            HttpMethod.Get, "MyBackEnd", "weatherforecast");

test code:

[TestMethod("MyFrontEnd should call weatherforecast endpoint on MyBackEnd service")]
public async Task TestMethod1()
{
    //arrange
    var daprClientMock = new Mock<DaprClient>();
    var weatherForecastController = 
        new WeatherForecastController(Mock.Of<ILogger<weatherForecastController>>(), daprClientMock.Object);

    //act
    var forecasts = await weatherForecastController.Get();

    //assert
    daprClientMock.Verify(daprClient =>
        daprClient.InvokeMethodAsync<IEnumerable<string>>(HttpMethod.Get
            , "MyBackEnd", "weatherforecast", default(CancellationToken))
        , Times.Once());
}

library method source:

public abstract class DaprClient : IDisposable
{
    //snippet of method I'm invoking
    public Task<TResponse> InvokeMethodAsync<TRequest, TResponse>(HttpMethod httpMethod
        , string appId, string methodName, TRequest data
        , CancellationToken cancellationToken = default(CancellationToken))
    {
        HttpRequestMessage request = CreateInvokeMethodRequest(httpMethod
            , appId, methodName, data);
        return InvokeMethodAsync<TResponse>(request, cancellationToken);
    }
}

source

error message when running my current test:

Test method MyFrontEnd.BlahControllerTests.TestMethod1 threw exception: System.NotSupportedException: Unsupported expression: daprClient => daprClient.InvokeMethodAsync<IEnumerable>(HttpMethod.Get, "MyBackEnd", "weatherforecast", CancellationToken) Non-overridable members (here: DaprClient.InvokeMethodAsync) may not be used in setup / verification expressions.


My research/thinking

This is something I would want to test right? Service invocation verification is a valid use case when unit testing, right?

Assuming that is correct, the DaprClient is an abstract class (ok, I guess that works) but the method I'm calling isn't.

I'm guessing I have to call the abstract version of InvokeMethodAsync then? And then I should create my own method that does what the above code is doing so I don't create any DRY violations ⚠️?

But when I look into CreateInvokeMethodRequest, that's abstract so how the heck does this work then? 🤔


Solution

  • using @halspang's sample here, I've derived the following pattern:

    [TestMethod("mock dapr service invocation")]
    public async Task TestMethod1()
    {
        //arrange
        var daprClient = new Mock<DaprClient>();
        var exampleService = new ExampleService(daprClient.Object);
    
        var serviceInvocationRequest = new HttpRequestMessage();
    
        //normal setup: when called, return
        //this mocked resopnse;
        //BUT we have to make sure it's
        //the same request we're mocking
        //next in the CreateInvokeMethodRequest
        //setup.
        daprClient.Setup(d => d.InvokeMethodAsync<Customer>(serviceInvocationRequest
            , It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Customer
            {
                Name = "Mike D."
            });
    
        //b/c the DaprClient follows a pattern like
        //this: calling the more speficic flavors
        //of InvokeMethodAsync will call
        //the abstract CreateInvokeMethodRequest
        //method and then call the abstract
        //InvokeMethodAsync method,
        //we can use the knowledge of the internal
        //workings of the DaprClient (which
        //seems like code smell to me)
        //to indirectly verify the args
        //we used to call invoke the
        //the other service.
        //public Task InvokeMethodAsync(
        //    string appId,
        //    string methodName,
        //    CancellationToken cancellationToken = default)
        //{
        //    var request = CreateInvokeMethodRequest(appId, methodName);
        //    return InvokeMethodAsync(request, cancellationToken);
        //}
        daprClient.Setup(d => d.CreateInvokeMethodRequest(
            HttpMethod.Get, "my-cool-app", "customer"))
            .Returns(serviceInvocationRequest);
    
        var actual = await exampleService.IsTheCustomerNameMikeD();
    
        //assert
        actual.Should().BeTrue();
    }
    

    full project