Search code examples
xunitnsubstitute

xUnit - mocked method for the async List returns null


Here's the strange thing - when I run the Unit Test and I put the breakpoint on the line return Unit.Value;, then I see that the someResult value is null - but when I manually move the debugger's yellow arrow the the previous line again var someResult = await _myRepository.DoSomething(cancellationToken); then the someResult variable is not null and contains my object - why does that happen ?

Unit test snippet:

//Arrange
var myList = new List<MyTable> { new MyTable() };
var myRepository = Substitute.For<IMyRepository>();
myRepository.DoSomething(Arg.Any<CancellationToken>()).Returns(myList);

var command = Substitute.For<MyCommand>();

//Act
var sut = new MyCommandHandler(myRepository);
await sut.Handle(command, Arg.Any<CancellationToken>());

I also tried:

myRepository.DoSomething(Arg.Any<CancellationToken>()).Returns(Task.FromResult(myList));

The tested class:

public class MyCommandHandler : ICommandHandler<MyCommand, Unit>
{
    private readonly IMyRepository _myRepository;

    public MyCommandHandler(IMyRepository myRepository)
    {
        _myRepository = myRepository ?? throw new ArgumentNullException(nameof(myRepository));
    }

    public async Task<Unit> Handle(MyCommand command, CancellationToken cancellationToken)
    {
        var someResult = await _myRepository.DoSomething(cancellationToken);

        ...
        return Unit.Value;
    }
}

public class MyRepository : IMyRepository
{
    private readonly MyDbContext _context;

    public MyRepository(MyDbContext context)
    {
        _context = context;
    }

    public async Task<List<MyTable>> DoSomething(CancellationToken cancellationToken = default)
    {
        return await _context.MyTable
            .AsNoTracking()
            .Where(...)
            .ToListAsync(cancellationToken);
    }
}

Solution

  • I think the Arg.Any<CancellationToken>() matcher in your Handle invocation is the problem.

    Changing

    await sut.Handle(command, Arg.Any<CancellationToken>());
    

    to

    await sut.Handle(command, default);
    

    got the test working. The matchers are for configuring the substitute and checking the received invocations, not for the actual invocation itself.

    A working example can be found here. I didn't have enough to go on to replicate Unit and it's usage but was able to observe and resolve the same problem you were having without it.