Location>code7788 >text

.NET 8 Moq mock GetRequiredKeyedService Setupreport an error

Popularity:765 ℃/2024-08-30 15:42:45

.NET 8 Moq mock GetRequiredKeyedService Setupreport an error

There are places where the code uses theto parse the service, and you need to Mock it when writing unit tests, so I thought something like

var serviceProvider = new Mock<IServiceProvider>();
(x => <AAA>(<BBB>())).Returns(new CCC());

I didn't realize it was an error.

  Test method threw exception: 
  : Unsupported expression: x => (<Type>(), <object>())
  Extension methods (here: ) may not be used in setup / verification expressions.

Stack Trace: 
  (MethodInfo method, Expression expression) line 87
  (LambdaExpression expression, MethodInfo method, IReadOnlyList`1 arguments, Boolean exactGenericTypeArguments, Boolean skipMatcherInitialization, Boolean allowNonOverridable) line 236
  ExpressionExtensions.<Split>g__Split|5_0(Expression e, Expression& r, MethodExpectation& p, Boolean assignment, Boolean allowNonOverridableLastProperty) line 256
  (LambdaExpression expression, Boolean allowNonOverridableLastProperty) line 170
  [TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) line 728
  (Mock mock, LambdaExpression expression, Condition condition) line 562
  Mock`[TResult](Expression`1 expression) line 645

It's kind of weird, isn't it?GetRequiredKeyedServiceNot an interface method? Checking the .NET source code, sure enough, theGetRequiredKeyedServiceis the IServiceProvider expansion method, and we know that Moq does not support the Setup expansion method.

/// <summary>
/// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <param name="serviceKey">An object that specifies the key of service object to get.</param>
/// <returns>A service object of type <typeparamref name="T"/>.</returns>
/// <exception cref="">There is no service of type <typeparamref name="T"/>.</exception>
public static T GetRequiredKeyedService<T>(this IServiceProvider provider, object? serviceKey) where T : notnull
{
    (provider);

    return (T)(typeof(T), serviceKey);
}

It's half as good as finding the cause, looking through the source code and finding it step by stepThe final interface method to call and then mocke can be

First look at what method is called by (serviceType, serviceKey)

  /// <summary>
  /// IKeyedServiceProvider is a service provider that can be used to retrieve services using a key in addition
  /// to a type.
  /// </summary>
  public interface IKeyedServiceProvider : IServiceProvider
  {
      /// <summary>
      /// Gets the service object of the specified type.
      /// </summary>
      /// <param name="serviceType">An object that specifies the type of service object to get.</param>
      /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
      /// <returns> A service object of type serviceType. -or- null if there is no service object of type serviceType.</returns>
      object? GetKeyedService(Type serviceType, object? serviceKey);

      /// <summary>
      /// Gets service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/> implementing
      /// this interface.
      /// </summary>
      /// <param name="serviceType">An object that specifies the type of service object to get.</param>
      /// <param name="serviceKey">The <see cref=""/> of the service.</param>
      /// <returns>A service object of type <paramref name="serviceType"/>.
      /// Throws an exception if the <see cref="IServiceProvider"/> cannot create the object.</returns>
      object GetRequiredKeyedService(Type serviceType, object? serviceKey);
  }
  

You can see that IKeyedServiceProvider is also inherited from the IServiceProvider interface, it's better to do, we directly Mock IKeyedServiceProvider and Setup can be used, will use the IServiceProvider place, change to the IKeyedServiceProvider, the code is as follows:

var serviceProvider = new Mock<IKeyedServiceProvider>();
(x => (<AAA>(), <BBB>())).Returns(new CCC());

Run the test, perfect.

summarize

It's not difficult to solve this problem, but it's still a bit of a headache if .Net isn't open source and you can't see the source code.