I'm migrating from Moq to Nsubstitute and faced this problem. In Moq I have an unit test to verify that the grpc method was called once. Moving to NSubstitute the assertion throws NullReferenceException. I read that NSubstitute is not meant for classes but interfaces or virtual methods. I think in this case it meet the requirements. The grpc methods are from a nuget that I implement in my code.
I tested adding more calls or reduce them and NSubstitute throws the expected error, something like "Expected to receive exactly 2 calls matching, Actually received 1 matching call". So, the problem is just when I want to assert exactly how many calls I expect the method was executed.
Here is the test in question, commented lines are from Moq
[TestClass]
public class MyRepositoryTests : TestBaseV2
{
private GrpcService.GrpcServiceClient grpcClient;
private IMyRepositoryConverter myRepositoryConverter;
private ILogger<MyRepository> logger;
[TestInitialize]
public void Init()
{
grpcClient = Substitute.For<GrpcService.GrpcServiceClient>();
myRepositoryConverter = Substitute.For<IMyRepositoryConverter>();
logger = Substitute.For<ILogger<MyRepository>>();
}
[TestCleanup]
public void CleanUp()
{
grpcClient = null;
myRepositoryConverter = null;
logger = null;
}
public MyRepository GetMyRepository() => new(grpcClient, myRepositoryConverter, logger);
[TestMethod]
public async Task DeleteObjectAsync_ExecutesWithSuccess_NoError()
{
Guid Id = Guid.NewGuid();
var grpcResponse = AutoFixture.Build<ObjectResponse>()
.With(r => r.Success, true)
.With(r => r.Error, "")
.Create();
var mockCall = TestCalls.AsyncUnaryCall(Task.FromResult(grpcResponse),
Task.FromResult(new Metadata()),
() => Status.DefaultSuccess,
() => new Metadata(),
() => { });
// grpcClient.Setup(c => c.GrpcDeleteObjectAsync(It.IsAny<ObjectRequest>(), null, null, CancellationToken.None)).Returns(mockCall);
grpcClient.GrpcDeleteObjectAsync(Arg.Any<ObjectRequest>(), null, null, CancellationToken.None).Returns(mockCall);
var repo = GetMyRepository();
await repo.DeleteObjectAsync(Id);
// grpcClient.Verify(c => c.GrpcDeleteObjectAsync(It.IsAny<ObjectRequest>(), null, null, CancellationToken.None), Times.Once());
try
{
await grpcClient.Received(1).GrpcDeleteObjectAsync(Arg.Any<Request>(), null, null, CancellationToken.None);
}
catch (NullReferenceException) { }
}
}
This is what I see when navigate to GrpcDeleteObjectAsync method
[GeneratedCode("grpc_csharp_plugin", null)]
public virtual AsyncUnaryCall<ObjectResponse> GrpcDeleteObjectAsync(ObjectRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
{
return GrpcDeleteObjectAsync(request, new CallOptions(headers, deadline, cancellationToken));
}
I raised the same question in the git repository. Short answer: await returns an instance of a null Task. https://github.com/nsubstitute/NSubstitute/issues/735 Recommended workaround is remove await, I'll start using the discard variable instead of a try-catch in these scenarios from this
try
{
await grpcClient.Received(1).GrpcDeleteObjectAsync(Arg.Any<Request>(), null, null, CancellationToken.None);
}
catch (NullReferenceException) { }
to this
_ = grpcClient.Received(1).GrpcDeleteObjectAsync(Arg.Any<Request>(), null, null, CancellationToken.None);