Reputation: 2419
I have function to mock http client:
private void MockClient(HttpStatusCode status, HttpContent content = null)
{
_client
.Setup(m => m.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = status, Content = content})).Verifiable();
}
And I use it tests like this:
[Fact]
public async void Get_SerializerFailure_Throws()
{
var content = new MemoryStream(new byte[5]);
MockClient(HttpStatusCode.OK, new StreamContent(content));
_jsonSerializer.Setup(x => x.DeserializeFromStream<Dto>(It.IsAny<Stream>())).Throws<JsonReaderException>();
await Assert.ThrowsAsync<JsonReaderException>(() => _sut.GetAllAsync());
}
here I get error:
typeof(System.ObjectDisposedException): Cannot access a disposed object.
Object name: 'System.Net.Http.StreamContent'.
However, if I mock client directly in the test like this it works and doesn't throw this error:
[Fact]
public async void Get_ProtobufSerializerFailure_Throws()
{
var content = new MemoryStream(new byte[5]);
_client
.Setup(m => m.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StreamContent(content)})).Verifiable();
_protobufSerializer.Setup(x => x.DeserializeFromStream<Dto>(It.IsAny<Stream>())).Throws<JsonReaderException>();
await Assert.ThrowsAsync<JsonReaderException>(() => _sut.GetAllAsync());
}
I do not get what is the difference here and why one way works while other does not and how ti fix my mock method.
I am testing these methods:
private async Task<Object> GetLatest()
{
using (var response = await _client.Get($"{_serviceUrl}", CancellationToken.None))
using (var stream = await response.Content.ReadAsStreamAsync())
{
return _jsonSerializer.DeserializeFromStream<Objects>(stream)?.Items.Last();
}
}
public async Task<IReadOnlyList<(ulong key, Dto item)>> GetAllAsync()
{
var latest = await GetLatest();
using (var response = await _client.Get(url,CancellationToken.None))
using (var stream = await response.Content.ReadAsStreamAsync())
using (var decompressionStream = new GZipStream(stream, CompressionMode.Decompress))
{
var result = _protobufSerializer.DeserializeFromStream<Dto>(decompressionStream);
return result.ToList();
}
}
Upvotes: 3
Views: 5384
Reputation: 806
The different between these two test setups is how many instances of StreamContent
class are created during the test execution.
HttpResponseMessage
class with the reference to the same single instance of StreamContent
passed as a parameter to MockClient
method..Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = status,
Content = content}))
HttpResponseMessage
class, one per _client.Get
method execution, but every HttpResponseMessage
instance has own instance of StreamContent
..Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = HttpStatusCode.OK,
Content = new StreamContent(content)}))
In the provided code, the _client.Get
method is called twice during the single test execution. The first time in GetLatest
method and the second time in GetAllAsync
method. Each method reads response content and disposes it. As result, in case of the first setup, GetAllAsync
method receives ObjectDisposedException
on attempt to read response content as a stream, as the single instance of response content has been already disposed in GetLatest
method.
All disposable objects should be created as many times as they are retrieved and disposed in the code under test.
As a possible solution, the MockClient
method should be changed to accept byte array instead of StreamContent and create separate StreamContent and MemoryStream instances for each HttpResponseMessage
response.
private void MockClient(HttpStatusCode status, byte[] content = null)
{
_client
.Setup(m => m.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(
new HttpResponseMessage
{
StatusCode = status,
Content = content == null
? null
: new StreamContent(new MemoryStream(content))
}))
.Verifiable();
}
[Fact]
public async Task Get_SerializerFailure_Throws()
{
MockClient(HttpStatusCode.OK, new byte[5]);
_jsonSerializer.Setup(x => x.DeserializeFromStream<Dto>(It.IsAny<Stream>())).Throws<JsonReaderException>();
await Assert.ThrowsAsync<JsonReaderException>(() => _sut.GetAllAsync());
}
Another option is to pass Func<StreamContent>
delegate to MockClient
method.
private void MockClient(HttpStatusCode status, Func<StreamContent> content = null)
{
_client
.Setup(m => m.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(
new HttpResponseMessage
{
StatusCode = status,
Content = content == null
? null
: content()
}))
.Verifiable();
}
[Fact]
public async Task Get_SerializerFailure_Throws()
{
MockClient(HttpStatusCode.OK, () => new StreamContent(new MemoryStream(new byte[5])));
_jsonSerializer.Setup(x => x.DeserializeFromStream<Dto>(It.IsAny<Stream>())).Throws<JsonReaderException>();
await Assert.ThrowsAsync<JsonReaderException>(() => _sut.GetAllAsync());
}
Upvotes: 4