Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup generic dynamic return #1295

Closed
matthiaslischka opened this issue Nov 2, 2022 · 2 comments
Closed

Setup generic dynamic return #1295

matthiaslischka opened this issue Nov 2, 2022 · 2 comments
Labels

Comments

@matthiaslischka
Copy link

matthiaslischka commented Nov 2, 2022

I have an interface that looks like this:

public interface ISomeRepository
{
    Task<ICollection<TResult>> GetAllAsync<TResult>(Expression<Func<Account, TResult>> selector, CancellationToken cancellationToken = default) where TResult : class;
}

I can pass an selector func to get exactly the data structure that I need.
e.g.:

ICollection<AccountDto> allAccounts = await someRepositoryMock.Object.GetAllAsync(x => new AccountDto { Name = x.Name }, CancellationToken.None);

For this I can write the setup like:

var someRepositoryMock = new Mock<ISomeRepository>();

var accounts = new List<Account>
{
    new() { Name = "Foo" },
    new() { Name = "Bar" }
};

someRepositoryMock.Setup(x => x.GetAllAsync(It.IsAny<Expression<Func<Account, AccountDto>>>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync((Expression<Func<Account, AccountDto>> expr, CancellationToken _) =>
    {
        var compile = expr.Compile();
        return accounts.Select(x => compile.Invoke(x)).ToList();
    });

But I don't know how to setup for dynamic return types. (afraid its not possible)
So my real call looks like this where I return just an anonymous class and not a DTO in the selection func:

var allAccounts = await someRepositoryMock.Object.GetAllAsync(x => new  { x.Name }, CancellationToken.None);

Now I can not simply change the setup type to dynamic/object because then the return method is never called:

someRepositoryMock.Setup(x => x.GetAllAsync(It.IsAny<Expression<Func<Account, dynamic>>>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync((Expression<Func<Account, dynamic>> expr, CancellationToken _) =>
    {
        var compile = expr.Compile();
        return accounts.Select(x => compile.Invoke(x)).ToList();
    });

And when I make the setup with It.IsAny<Expression<Func<Account, It.IsAnyType>>>() I don't know how to write the return correctly because it forces return type TResult to be It.IsAnyType which is not possible.

Full unit test to play around: https://gist.github.com/matthiaslischka/f1aa29e285c75af13e5851d71b080ce8

Is what I want even possible?!
Do you see a better way? I don't want to use DTOs in my production code just for test setup purpose...

BR Matthias

@stakx
Copy link
Contributor

stakx commented Dec 12, 2022

Remember that dynamic is essentially a feature of the .NET compilers, but to the runtime, it is the same thing as System.Object. Since Moq version 4 does all of its work exclusively at runtime, it only sees System.Object wherever your source code mentions dynamic.

Generally speaking, Moq doesn't really have any support for dynamic, because one of its explicit design goals was static type safety, and dynamic and all of its late binding trickery directly goes against that. So I can't really offer you a good solution.

What you could try (but it's probably not going to be very pretty) is to look into It.IsAnyType placeholders – try using It.IsAnyType in place of dynamic inside your setup expression, and give your ReturnsAsync lambda a single (IInvocation invocation) parameter. In the lambda's body, you can then inspect the actual runtime Expression<> type via invocation.Method.GetParameters().First().ParameterType and do a bunch of reflection to produce a value of the appropriate type.

@stakx stakx added the question label Dec 12, 2022
@stakx
Copy link
Contributor

stakx commented Dec 30, 2022

Closing this issue due to inactivity. If the above post was unclear, let me know. Otherwise I'm hoping you found a way to get your work done.

@stakx stakx closed this as completed Dec 30, 2022
@devlooped devlooped locked and limited conversation to collaborators Sep 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants