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