Search code examples
.netxunitnsubstitute

How to return one of the parameters of a NSubstitute mocked method that is async. System.InvalidCastException


I have the following code that compiles.

public class Test
{
    [Fact]
    public async Task ThisShouldWork()
    {
        // arrange
        var dto = new Dto
        {
            FName = "John",
            LName = "Doe"
        };
        
        var mock = Substitute.For<IRepository>();
        mock
            .SaveAsync(Arg.Any<DomainObject>())
            .Returns(args => args[0]);
        var service = new BusinessService(mock);
        
        // act
        var result = await service.DoSomethingAsync(dto);
        
        // assert
        await mock
            .Received(1)
            .SaveAsync(result);
    }
}

public class BusinessService
{
    private readonly IRepository _repository;

    public BusinessService(IRepository repository)
    {
        _repository = repository;
    }

    public async Task<DomainObject> DoSomethingAsync(Dto dto)
    {
        var domain = new DomainObject
        {
            FirstName = dto.FName,
            LastName = dto.LName
        };

        return await _repository.SaveAsync(domain);
    }
}

public class DomainObject
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public interface IRepository
{
    Task<DomainObject> SaveAsync(DomainObject domain);
}

public class Dto
{
    public string FName { get; set; }
    public string LName { get; set; }
}

However, if I run the test I get a System.InvalidCastException.

Unable to cast object of type 'DomainObject' to type 'System.Threading.Tasks.Task`1[DomainObject]'

What am I doing wrong?


Solution

  • Use

    .Returns(args => args.Arg<DomainObject>());
    

    or alternatively

    .Returns(args => args.ArgAt<DomainObject>(0));
    

    The problem with args[0] is that it returns object and therefore the compiler won't take the Returns<T>() extension method overload on Task<T> which automatically wraps the result in a task for you.

    An explicit cast should also work, but I don't like such code:

    .Returns(args => (DomainObject)args[0]);