简体   繁体   中英

Mocking generic return type in generic method with a expression

I have the following interface with a generic method.

public interface IRepository {
  Task<TResult> GetByIdAsync<TResult>(
    int id,
    Expression<Func<Entity, TResult>> expression,
    TResult defaultValue = default);
}

In some parts of the code expression returns Tuple and anonymous type.

Example

var _repository = ServiceProvider.GetRequiredService<IRepository>();

var data = await _repository.GetByIdAsync(id, pr => Tuple.Create(pr, pr.SelfRefObject));
var data = await _repository.GetByIdAsync(id, pr => new { pr.SelfRefObjectId });

Is it possible to mock the method so it returns specified value with mentioned types?

Example

var _repositoryMock = new Mock<IRepository>();

var anonymousReturnValue = new { SelfRefObjectId = 1 };

 _repositoryMock
  .Setup(pr => pr.GetByIdAsync(
    entityId,
    pr => new { pr.SelfRefObjectId },
    default))
  .ReturnsAsync(anonymousReturnValue)
  .Verifiable();

var tupleReturnValue = Tuple.Create(new Entity(), new Entity());

 _repositoryMock
  .Setup(pr => pr.GetByIdAsync(
    entityId,
    pr => Tuple.Create(pr, pr.SelfRefObject),
    default))
  .ReturnsAsync(tupleReturnValue)
  .Verifiable();

Thanks in advance.

In short: according to my knowledge you can't do this.

There are two really useful helper types: It.IsAnyType and It.IsSubType<T> .

During the setup phase you could use them to tell that your function accepts anything:

_repositoryMock
    .Setup(repo => repo.GetByIdAsync(
        It.IsAny<int>(), 
        It.IsAny<Expression<Func<Entity, It.IsAnyType>>>(), 
        It.IsAny<It.IsAnyType>()))
    .Verifiable();

Or it accepts only anonymous type or tuple:

_repositoryMock
    .Setup(repo => repo.GetByIdAsync(
        It.IsAny<int>(), 
        It.Is<Expression<Func<Entity, It.IsAnyType>>>((o, type) => type.IsAnonymousType()), 
        It.Is<It.IsAnyType>((o, type) => type.IsAnonymousType())))
    .Verifiable();

_repositoryMock
    .Setup(repo => repo.GetByIdAsync(
        It.IsAny<int>(), 
        It.Is<Expression<Func<Entity, It.IsAnyType>>>((o, type) => type.IsTuple()), 
        It.Is<It.IsAnyType>((o, type) => type.IsTuple())))
    .Verifiable();

Helper methods:

public static class TypeExtension
{
    public static bool IsAnonymousType(this Type type)
    {
        var hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any();
        var nameContainsAnonymousType = type.FullName != null && type.FullName.Contains("AnonymousType");
        return hasCompilerGeneratedAttribute && nameContainsAnonymousType;
    }

    public static bool IsTuple(this Type type)
    {
        if (!type.IsGenericType) return false;
        var openType = type.GetGenericTypeDefinition();
        return openType == typeof(Tuple<>) || openType == typeof(Tuple<,>) 
               || openType == typeof(ValueTuple<>) || openType == typeof(ValueTuple<,>);
    }
}

The problem comes when you need to specify the Return/ReturnAsync part.

_repositoryMock
    .Setup(repo => repo.GetByIdAsync(
        It.IsAny<int>(), 
        It.Is<Expression<Func<Entity, It.IsAnyType>>>((o, type) => type.IsTuple()), 
        It.Is<It.IsAnyType>((o, type) => type.IsTuple())))
    .Returns(valueFunction: () => Task.FromResult(tupleReturnValue));

It won't compile because it can't convert Tuple<Entity, Entity> to Mock.It.IsAnyType

Severity Code Description Project File Line Suppression State Error CS0029 Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Tuple<anonym.Entity, anonym.Entity>>' to 'System.Threading.Tasks.Task<Moq.It.IsAnyType>'

Even the related github issue is closed the Type resolver does not seems to work.


Alternative approach could be making use of DefaultValueProvider .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM