Search code examples
c#unit-testingasync-awaitmoqxunit

Getting NullReferenceException while mocking Async method in Unit Test (Xunit - Moq)


I am having an issue while testing one of the service method which uses an async Repository method.

Repository layer

public interface IRepo
{
    Task<Model> GetByIdAsync(string Id);
    Task SaveAsync(Model model);
}

Service layer

void Process(string Id)
{
    var model = _repo.GetByIdAsync(Id).Result;
        
    model.Field1 = "update";
        
    _repo.SaveAsync(model).Wait();
}

Unit test against service layer

[Fact]
SUTServiceTest()
{
    //Arrange
    Model model = new Model();
    var mockRepo = Mock.Get(new Repo());

    mockRepo.Setup(x => x.GetByIdAsync(It.IsNotNull<string>()))
        .Returns(() => Task.FromResult(model));

    mockRepo.Setup(x => x.SaveAsync(It.IsAny<Model>()))
        .Callback<Model>((obj) => model = obj);

    var _service = new SUTService(mockRepo);

    //Act
    _service.Process("1");

    //Assert
    Assert.Equal("update", model.Field1);
}

I'm getting the following error at _repo.SaveAsync(model).await();:

System.NullReferenceException - Object reference not set to an instance of an object

Not sure what did I miss.


Solution

  • The intent behind this answer is to capture valuable information from the chat discussion.

    As the OP said a NullReferenceException has been thrown at the following line:

    _repo.SaveAsync(model).Wait();
    

    The _repo tried to be initialized in the following way in the unit test:

    var mockRepo = Mock.Get(new Repo());
    

    That's not the correct way to do it. Please check the documentation of the Mock.Get to know when it should be used and for what purpose.

    The _repo should be initialized in the following way:

    var mockRepo = new Mock<IRepo>();
    

    The OP had the following observation after changing the mock creation:

    However, I loose the DI setup that I have for Repo construction

    In short: That's the whole point.
    Whenever you are unit testing a component then you try to mock out (simplify and mimic) all of its dependencies. It needs to rely on stubs and spies to be able to verify which one of them have been called under certain circumstances but the concrete implementation of the mocked objects do not matter.

    In this particular case that means that the Service layer do not want to rely on a concrete Repository instance. It needs only a repo, which can capture the calling parameters and which can verify its calling.


    FYI: Testing terminologies

    • Dummy: simple code that returns bogus data
    • Fake: a working alternative which can take shortcuts
    • Stub: custom logic with predefined data
    • Mock: custom logic with expectations (interactive stub)
    • Shim: custom logic at run-time
    • Spy: interceptors to record calls