Check calls Received() for async method

21.4k views Asked by At

When I run the following code:

[Test]
public async Task Can_Test_Update()
{
    var response = await _controller.UpdateAsync(Guid.NewGuid());
    response.Valid.Should().BeTrue();

    _commands.Received().UpdateAsync(
        Arg.Is<Something>(
            l => l.Status == Status.Updated)); 
}

If I add "await" preceding the "_commands.Received().UpdateAsync", it throws a null reference exception. How can I stop this happening, or is await not necessary?

8

There are 8 answers

2
letitbe On BEST ANSWER

I found an answer here.

Received.InOrder(async () =>
{
    await _Commands.UpdateAsync(Arg.Is<Lobby>(l => l.Status == Status.Updated));
});
1
Jake Ginnivan On

When NSubstitute sees an async call it automatically creates a completed task so the await works as you would expect in your code (and not throw a NullReferenceException). In this case that would be the task returned from _commands.UpdateAsync(Status.Updated)) inside the method you are testing.

The .Received() call on the other hand is verifying that the async method was called, that is fully synchronous so it doesn't need to be awaited.

The key thing to remember is that async methods return a Task. Calling the async method and returning the task is fully synchronous, you then await the Task to know when the asyncronous operation which the task represents is completed.

1
Yuval Itzchakov On

when i add "await" preceding the "_commands.Received().UpdateAsync", it occurs error null reference

That's because when you don't await, the method (Can_Test_Update) may end before it actually checks the null value you're passing to the method, which means the test ends. You have a race condition. When you await on UpdateAsync, the method actually asynchronously waits for the operation to complete, and UpdateAsync gets a chance to access the null you're passing to it.

To resolve your error, simply put a breakpoint inside UpdateAsync and see which value is passed as null to the method. I suspect Arg.Is<Something> is your problem.

3
Andrei Tătar On

If UpdateAsync is a stubbed method, you need to return an empty Task, not null. You can not await a null Task.

Example:

receivedObject.Stub(s => s.Update(...)).Return(Task.FromResult(0));

Edit

The problem is at this line:

var mockService = Substitute.For<ICalculationServiceAsync>(); 

Or more exactly, when you call it's method:

await _service.Calculate();

You create a mock service but you don't stub the method. I'm not sure how to do it in Nunit (we mainly use Rhino, I'll need to check), but you need to stub your Calculate method to return an empty Task (Task.FromResult(0)). By default, stubbed methods return the default return type and default(Task) is null.

About your gist: DoSomethingAsync shouldn't be async void. I assume you would want to await it's execution.

0
Grengas On

Apparently you can simply await the Received method:

[Test]
public async Task Can_Test_Update()
{
    var response = await _controller.UpdateAsync(Guid.NewGuid());
    response.Valid.Should().BeTrue();

    await _commands.Received().UpdateAsync(
        Arg.Is<Something>(
            l => l.Status == Status.Updated)); 
}
0
pipedreambomb On

According to this answer on Stack Overflow, as of NSubstitute version 1.8.3 you can use await and it will work as expected, rather than throwing a NullReferenceException.

I've just tried it out as I was on version 1.5.0 and getting the NullReferenceException as you describe, but now I'm on the latest (1.10.0), it's working well.

1
HariVamshi vallamkonda On

I have observed this issue in NSubsittute 1.8.3, it seems its fixed in 1.9.0 version or higher.

4
Michael Freidgeim On

The Jake Ginnivan answer correctly points that for Received await is not required, however compiler doesn’t understand it and shows

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

The simplest workaround is to suppress warning

 #pragma warning disable 4014 //for .Received await is not required, so suppress warning “Consider applying the 'await' operator”
   _publisher.Received(totalNumber).MyMethod(Arg.Any<ParamType>());
 #pragma warning restore 4014