user122222
user122222

Reputation: 2419

Cannot access a disposed object in unit test

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

Answers (1)

Botan
Botan

Reputation: 806

The different between these two test setups is how many instances of StreamContent class are created during the test execution.

  • The first setup creates multiple instance of 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}))
  • The second setup also creates multiple instance of 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.


SOLUTION 1

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());
}


SOLUTION 2

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

Related Questions