Azure-functions-durable-extension: Provide unit testing instructions for interfaces in Durable 2.x

Created on 29 Oct 2019  路  11Comments  路  Source: Azure/azure-functions-durable-extension

Description

while trying to setup a mock using MOQ with IDurableOrchestrationContext I get the below exception:

Unsupported expression: it => it.CallActivityAsync(It.IsAny<string>(), It.IsAny<object>())
    Extension methods (here: DurableContextExtensions.CallActivityAsync) may not be used in setup / verification expressions.
  Stack Trace: 
    Guard.IsOverridable(MethodInfo method, Expression expression)
    InvocationShape.ctor(LambdaExpression expression, MethodInfo method, IReadOnlyList`1 arguments, Boolean exactGenericTypeArguments)
    ExpressionExtensions.<Split>g__Split|4_1(Expression e, Expression& r, InvocationShape& p)
    ExpressionExtensions.Split(LambdaExpression expression)
    Mock.GetMatchingInvocationCount(Mock mock, LambdaExpression expression, List`1& invocationsToBeMarkedAsVerified)
    Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
    Mock`1.Verify[TResult](Expression`1 expression, Func`1 times)

Expected behavior

no exception to be thrown and mock get init correctly during setup

Actual behavior

Mentioned exception has been thrown

Relevant source code snippets

  durableOrchestrationContextMock = new Mock<IDurableOrchestrationContext>();
            durableOrchestrationContextMock
                .Setup(x => x.CallActivityAsync(It.IsAny<string>(), It.IsAny<BaseJobInfo>()))
                .Returns(Task.CompletedTask);

App Details

  • Durable Functions extension version 1.0.0:
  • Microsoft.Azure.WebJobs.Extensions.DurableTask version 2.0.0-beta3:
  • Programming language used: c#
documentation

All 11 comments

@hunyhabib

This is an unfortunate side affect of our new design in Durable Functions 2.0. We moved from abstract classes to interfaces, and in order to add support for many of our overloads that just call other core methods, we had to use extension methods instead of virtual methods with a default implementation.

This works well until Moq comes into the scene. With our old approach, since the methods were virtual, Moq was able to override the implementation. However, with extension methods, they are simply syntactic sugar for static methods, so there is no way for Moq to override them.

Therefore, instead of mocking the extension method, you have to mock the actual method on IDurableOrchestrationContext that is called by the extension method. In your case, it would be Task<TResult> CallActivityAsync<TResult>(string functionName, object input);

I'm not sure how much information Visual Studio gives you over what is extension methods and what aren't, and if they are extension methods, what the implementation looks like, so I understand this is somewhat difficult. We may need to come up with creative solutions on our end to help make this scenario less painful.

Will try to override then the async method.. But there should be as you said a better way as its tricky specially when migrated to the newer version we started to get issues

One way to help at least in the meantime is to look into using this analyzer. The analyzer "Moq1200" should give you a warning when you try to override a method that is not overrideable (i.e. extension methods).

I was able to moq IDurableOrchestrationContext or Tuple<string,customcontext>() using It.isAny<object>() nothing else worked.

E.g.

.Setup(x => x.CallActivityAsync(It.IsAny<string>(), It.isAny<object>()))

Oh and don't use void azure functions if you want to moq and override them for unit testing. You can't verify them.

hej @ConnorMcMahon why you had to use extension methods instead of virtual, did the extension methods benefit in this case ? i feel it got more obstacles while upgrading more than helping .. and conceptually isn't overloading here is better than extension ? :)

@hunyhabib,

Good question. The reason we went with extension methods is due to our transition to interfaces from abstract classes. We did this transition due to the addition of entities, which have much shared logic with orchestrations, and an object can support multiple interfaces but cannot inherit from multiple abstract classes. If we were on C# 8, we could have used default implementations instead, but we are stuck on .NET Standard 2.0, so we were stuck with extension methods.

I think this is a case where if we used composition instead of inheritance, we could have avoided this problem. Unfortunatley, now that we can't make breaking changes in 2.0, it is a bit more challenging to retroactively solve this issue. I will look into seeing if we can provide an abstract class alternative to IDurableOrchestrationContext (and maybe IDurableEntityContext) and their extension methods to see if we can still enable mocking of all of our methods, not just the interface methods.

After deliberation on our team, we realized that it would be too much work to maintain an abstract class implementation alongside the interfaces.

Instead, I will make sure we update the documentation with best practices to make unit testing on v2.x easier.

Any chance of providing a link to the documentation @ConnorMcMahon? I have just upgraded to 2.2.2 and hit this issue straight away.

@Andy65,

This Stack Overflow answer the best place we have written instructions for at the moment unfortunately. We have not invested in documentation here because we are hoping to make the unit testing scenario much easier in our 2.3.0 release.

I hope that provides enough guidance in the meantime. Once we move off of extension methods the use of Moq should make everything substantially easier.

@ConnorMcMahon

we are hoping to make the unit testing scenario much easier in our 2.3.0 release.

Is there an approximate timeframe for that?

I have opened a PR for this. We are hoping the release timeframe for 2.3.0 will be in the next week or two.

Was this page helpful?
0 / 5 - 0 ratings