Mocking ItemResponse in CosmosDB
Asked Answered
C

1

5

I am writing unit tests using Moq in xUnit for a service that involves CosmosDB. There's a method GetVehicleInfo which returns ItemResponse<VehicleInfo> from CosmosDB. As ItemResponse has a protected constructor so I can't new it up. Therefore, I'm mocking the caller method and doing

var responseMock = new Mock<ItemResponse<VehicleInfo>>();
responseMock.Setup(x => x.Resource).Returns(expectedItem); //expectedItem is of VehicleInfo type
cosmoRepoServiceStub.Setup(service => service.GetVehicleInfo("a1", "a2").Result).Returns(responseMock.Object);

The problem I face is that when GetVehicleInfo is called as below, it returns null always. I expect it to return ItemResponse<VehicleInfo> wherein Resource will contain expectedItem.

ItemResponse<VehicleInfo> response = await _cosmosRepo.GetVehicleInfo(plate, country);
if (response == null){ //... }
Cesspool answered 10/2, 2022 at 11:40 Comment(0)
E
6

You should setup your cosmoRepoServiceStub like this:

cosmoRepoServiceStub
    .Setup(service => service.GetVehicleInfo(It.IsAny<string>(), It.IsAny<string>()))
    .ReturnsAsync(responseMock.Object);
  • GetVehicleInfo parameters should any string in the setup method
  • Instead of calling the .Result inside the method selector please prefer ReturnsAsync

Or if you really need to anticipate "a1" as a first argument then define it as

const StringComparison comparison = StringComparison.OrdinalIgnoreCase;
cosmoRepoServiceStub
    .Setup(service => service.GetVehicleInfo(
       It.Is<string>(param1 => string.Equals(param1, "a1", comparison), 
       It.Is<string>(param1 => string.Equals(param1, "a2", comparison)))
    .ReturnsAsync(responseMock.Object);

UPDATE #1 Reflect to comment

Why does It.IsAny work whereas "a1" does not?

Moq uses uses object.Equals under the hood to check the Setup's argument against the actual invocation's argument.

This means that the comparison for value types and for strings are based on their values (not based on their references).

So, in your particular case that means either plate or country does not contain a1 or a2 strings respectively.

In short I should work, but as a general rule of thumb

  • Make your Setup as generic as possible
  • Make your Verify as specific as possible
Emend answered 10/2, 2022 at 15:42 Comment(4)
In more recent updates to the documentation the second bullet point you made has been superseded. It does not make your answer incorrect in any way. was just bringing the updated documentation to your attention.Exedra
@Exedra Ohh thanks, I did not know about it. Good to know thanks!Emend
@PeterCsala Thank you for the answer. Initially, I was using ReturnsAsync but it didn't work with the hardcoded arguments to the GetVehicleInfo method. Just by changing it to It.IsAny<string>(), the test now starts to work. I fail to understand this. Can you please explain a bit? I had a look at the documentation but it's not explained there. I am confused because another test with hardcoded argument works fine. Thanks a bunch.Cesspool
@Cesspool I've updated my post to reflect to your question, please check it. If you have found it useful then please consider to upvote and/or accept it as the answer.Emend

© 2022 - 2024 — McMap. All rights reserved.