Search code examples
c#unit-testingmoqxunit

How do I write a unit test for this method that is called inside a loop?


I'm trying to test a method using xUnit and Moq. The public void DoSomeWork_Verify_GetJsonDataString_is_called() test is failing. Please can someone let me know what I am missing in the test/mocking. The GetJsonDataString is an API call that returns a JSON string.

public class A 
{
    private readonly IData _data;
    private readonly IApiCall _apiCall;
       
    public A (IData data, IApiCall apiCall)
    {
        _data = data;
        _apiCall = apiCall;
    }
       
    public void DoSomeWork
    {
        var apps = _data.GetListOfApps(); // test on this method call is passing
        foreach(var app in apps)
        {
            if(string.IsNullOrEmpty(app.AppId)) continue;
            string jsonString = _apiCall.GetJsonDataString("ApiEndpoint" + app.AppId); 
            // how do I test this method call? Note: I'm not using async, this is just a console app.
        }       
    }
}

//MyTest
public class TestA
{
    private readonly Mock<IData> _data;
    private readonly Mock<IApiCall> _apicall;

    public TestA()
    {
        _data = new Mock<IData>;
        _apiCall = new Mock<IApiCall>;
    }

    public void DoSomeWork_Verify_GetListOfApps_is_called() 
    {
        _data.Setup(x => x.GetListOfApps());
        var sut = new A(_data.Object, _apiCall.Object);
        sut.DoSomeWork();
        _data.Verify(x => x.GetListOfApps(), Times.AtLeastOnce);  // this passes
    }

    public void DoSomeWork_Verify_GetJsonDataString_is_called()
    {
        _apicall.Setup(x => x.GetJsonDataString(It.IsAny<string>()));
        var sut = new A(_data.Object, _apiCall.Object);
        sut.DoSomeWork();
        _apicall.Verify(x => x.GetJsonDataString(It.IsAny<string>()), Times.AtLeastOnce);  // Failing
    }  
} 

Solution

  • You need to mock GetListOfApps so that it actually returns something for you to loop over. To do that you need to make use of the Returns method.

    Assuming the method returns a List<App> your setup could look like the following:

    _data.Setup(x => x.GetListOfApps())
         .Returns(new List<App> { new App { AppId = "X" } });
    

    Now your method will recieve data to loop over and the method you are trying to verify will be called.

    Note that while you are setting up GetJsonDataString, you aren't giving it a return value. By default Moq will return null but you should probably mock that with a return value as well.

    _data.Setup(x => x.GetJsonDataString(It.IsAny<string>()))
         .Returns("some string");
    

    There are also overloads of Returns that accept a factory function and will take the arguments passed to the mocked function as input. You can use this if you want/need the return result to vary based on the input parameters.

    _data.Setup(x => x.GetJsonDataString(It.IsAny<string>()))
         .Returns(strParam => "some string" + strParam);
    

    Now when you're verifying, I'd advise against using It.IsAny when you can. Otherwise you are saying that you don't care how it's called. Suppose we wanted to verify it actually got called with the mocked value from above (AppId="X") you would do this:

    _apicall.Verify(x => x.GetJsonDataString("ApiEndPointX"), Times.Once);
    

    Now you've got a test that's not just checking whether or not the method was called, but actually validating it got called with the expected values.

    Side note: there is no reason for your test class to accept parameters in its constructor, especially of type Mock<>. You aren't even using them! And technically this should fail to even execute since you don't have an IClassFixture set up. You can get rid of the constructor completely (and assign the fields directly) or just remove the parameters:

    public TestA()
    {
        _data = new Mock<IData>();
        _apiCall = new Mock<IApiCall>();
    }