Reputation: 13
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
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);
}
}
IDependency
instance via the constructor.Calculate
method inside the PrivateApi
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
Upvotes: 2