28

Let's say I have the following class and an interface it depends on:

public class MyController
{
    private IRepository _repository;
    public MyController(IRepository repository)
    {
        _repository = repository;
    }

    public async Task MethodUnderTest(int someId)
    {
        var o = await _repository.FindById(someId);
        // update o
        await _repository.Commit();
    }
}

public interface IRepository
{
    Task Commit();
}

When I unit test this method I can do the following (using xUnit and Rhino Mocks):

[Fact]
public async Task MyTest()
{
    IRepository repositoryStub = MockRepository.GenerateStub<IRepository>();

    MyController controller = new MyController(repositoryStub);

    await controller.MethodUnderTest(1);
}

This fails with a System.NullReferenceException : Object reference not set to an instance of an object.

With the following StackTrace:

UnitTest.MyController.<MethodUnderTest>d__0.MoveNext() in 
\UnitTest\Class1.cs:line 35
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at \UnitTest\Class1.cs:line 20

Is it correct that this error occurs because the Commit() returns null and the statemachine generated for the async/await calls MoveNext() on a null?

I can fix this by doing something like:

repositoryStub.Expect(r => r.Commit()).Return(Task.FromResult<bool>(true);

But this feels a little strange.. I can use any T for FromResult<T> and the test will run. I can't find a FromResult method that will return a non-generic Task.

Does it matter what I use for T? Or should I fix this some other way?

Wouter de Kort
  • 39,090
  • 12
  • 84
  • 103
  • It doesn't matter what type `T` you use since `Task` extends `Task`. You could use `Task.Delay(0)` instead. If you don't care about the task itself you could change `MethodUnderTest` to return `_repository.Commit()` directly instead of creating an async method. – Lee Dec 21 '12 at 14:36
  • Repository.Commit() uses Entity Frameworks 6 new async features to save changes to the database. I use this from an ASP.NET MVC application. What do you mean by returning _repository.Commit() directly? – Wouter de Kort Dec 21 '12 at 14:41
  • I mean `MethodUnderTest` could be `return _repository.Commit();` if you remove the `async` modifier, so it will just return a null task in your tests if you haven't stubbed `_repository.Commit()`. – Lee Dec 21 '12 at 14:44
  • OK.. this example is then a little to small. I have multiple awaits inside the MethodUnderTest so I can't remove the async keyword just for the Commit() – Wouter de Kort Dec 21 '12 at 14:49

2 Answers2

48

You will need to return something; async Task methods cannot ever return null.

The T doesn't matter. You could declare a static readonly Task SuccessTask = Task.FromResult<object>(null); as a helper constant if you want. I have similar constants in my AsyncEx library.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
7

If you are targeting .NET Framework 4.6 or later, you can now use .Returns(Task.CompletedTask) instead which is slightly shorter.

Scott
  • 4,458
  • 1
  • 19
  • 27
Martin Dawson
  • 7,455
  • 6
  • 49
  • 92