Carolynne Padilha
Carolynne Padilha

Reputation: 13

Count number of times a recursive method is called using Moq

I have those two methods, SendMessage is a recursive and non-overridable and SendMessageUsingHttpClient is virtual so I can mock any return on my test class.

public HttpResponseMessage SendMessage(HttpRequestMessage httpRequestMessage, HttpStatusCode? httpStatusCode = null)
{
    // not relevant
    GetRandomTokenAndAddInMessageHeader(httpRequestMessage, httpStatusCode);

    var response = SendMessageUsingHttpClient(httpRequestMessage);

    if (response.StatusCode == HttpStatusCode.Unauthorized)
        SendMessage(httpRequestMessage, HttpStatusCode.Unauthorized);

    return response;
}

public virtual HttpResponseMessage SendMessageUsingHttpClient(HttpRequestMessage httpRequestMessage)
{
    return _httpClient.Send(httpRequestMessage, CancellationToken.None);
}

I need to test this line if (response.StatusCode == HttpStatusCode.Unauthorized), so here's my test method:

[TestMethod]
public void Send_MustReturn_UnauthorizedMock_Then_OkMock()
{
    var mockHttpResponseManager = new Mock<HttpRequestManager>();

    var expectedUnauthorizedResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized };
    var expectedOkResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.OK };

    mockHttpResponseManager.SetupSequence(m => m.SendMessageUsingHttpClient(It.IsAny<HttpRequestMessage>()))
        .Returns(expectedUnauthorizedResponse)
        .Returns(expectedOkResponse);

    mockHttpResponseManager.Object.SendMessage(new HttpRequestMessage());

    mockHttpResponseManager.Verify(v => v.SendMessage(It.IsAny<HttpRequestMessage>(), It.IsAny<HttpStatusCode?>()), Times.Exactly(2));
}

If I run this test, as SendMessage isn't virtual, it'll throw the following exception:

Unsupported expression: v => v.SendMessage(It.IsAny(), It.IsAny<HttpStatusCode?>()) Non-overridable members (here: HttpRequestManager.SendMessage) may not be used in setup / verification expressions.

And if I make SendMessage to virtual, Moq won't count the second (recursive) call, only the one that Moq itself called inside my test class.

Expected invocation on the mock exactly 2 times, but was 1 times: v => v.SendMessage(It.IsAny(), It.IsAny<HttpStatusCode?>()) Performed invocations: MockHttpRequestManager:1 (v): HttpRequestManager.SendMessage(Method: GET, RequestUri: '', Version: 1.1, Content: , Headers: {}, null)

Upvotes: 1

Views: 584

Answers (1)

Peter Csala
Peter Csala

Reputation: 22679

During unit testing we should mock the dependencies. We want to inject an object, which behaves as we want. So, rather than mocking the system under test (SUT) you should focus on mocking the dependency as it as stated by devNull as well.

For the sake of simplicity let's forget the HttpClient and have a more simple dependency:

public interface IDependency
{
    int Calculate(int input);
}

public class Dependency: IDependency
{
    public int Calculate(int input) => input + 1;
}

Now, let's simplify your SUT as well:

public class SUT
{
    public const int MagicNumber = 42;
    private readonly IDependency dependency;
    public SUT(IDependency dependency)
    {
        this.dependency = dependency;
    }

    public int PublicApi(int input)
    {
        var response = PrivateApi(input);

        if (response == MagicNumber)
            PublicApi(input);

        return response;
    }

    private int PrivateApi(int input)
    {
        return dependency.Calculate(input);
    }
}
  • So, we receive an IDependency instance via the constructor.
  • We are calling its Calculate method inside the PrivateApi
  • The PrivateApi is called via the PublicApi

In your test case what you really need is to mock the IDependency. Its behaviour should induce a recursive call of the PublicApi.

You can make the call count assessment on the mocked instance. (So, your mock can work as a spy as well).

[Fact]
public void GivenADependency_FirstReturnsMagicNumber_AndSecondReturnsDifferentValue_WhenICallPublicApi_ThenItCallsTheDependencyTwice()
{
    //Arrange
    var dependencyMock = new Mock<IDependency>();
    dependencyMock.SetupSequence(dep => dep.Calculate(It.IsAny<int>()))
        .Returns(SUT.MagicNumber)
        .Returns(SUT.MagicNumber - 1);

    var sut = new SUT(dependencyMock.Object);

    //Act
    sut.PublicApi(1);

    //Arrange
    dependencyMock.Verify(dep => dep.Calculate(It.IsAny<int>()), Times.Exactly(2));
}

For the sake of completeness I'm sharing here some terminology about test doubles

  • 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 (replace static with a delegate)
  • Spy: interceptors to record calls

Upvotes: 2

Related Questions