无法在单元测试中访问已处理的对象

我有模拟 http 客户端的功能:

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

我使用它进行这样的测试:

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

这里出现错误:

 typeof(System.ObjectDisposedException): Cannot access a disposed object.
Object name: 'System.Net.Http.StreamContent'.

但是,如果我像这样直接在测试中模拟客户端,它可以工作并且不会抛出此错误:

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

我不明白这里有什么区别,为什么一种方法有效而另一种方法无效,以及如何修复我的模拟方法。

我正在测试这些方法:

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();
            }
        }
dongyafei123 回答:无法在单元测试中访问已处理的对象

这两种测试设置的不同之处在于在测试执行期间创建了多少个 StreamContent 类的实例。

  • 第一个设置创建 HttpResponseMessage 类的多个实例,并引用作为参数传递给 StreamContent 方法的同一个 MockClient 实例。
.Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = status,Content = content}))
  • 第二种设置还创建了 HttpResponseMessage 类的多个实例,每个 _client.Get 方法执行一个,但每个 HttpResponseMessage 实例都有自己的 StreamContent 实例。
.Returns(() => Task.FromResult(new HttpResponseMessage {StatusCode = HttpStatusCode.OK,Content = new StreamContent(content)}))

在提供的代码中,_client.Get 方法在单个测试执行期间被调用两次。第一次在 GetLatest 方法中,第二次在 GetAllAsync 方法中。每个方法读取响应内容并对其进行处理。因此,在第一次设置的情况下,GetAllAsync 方法在尝试将响应内容作为流读取时接收 ObjectDisposedException,因为响应内容的单个实例已经在 GetLatest 方法中处理.

所有一次性对象的创建次数应与它们在被测代码中检索和处理的次数相同。


解决方案 1

作为一种可能的解决方案,MockClient 方法应更改为接受字节数组而不是 StreamContent,并为每个 HttpResponseMessage 响应创建单独的 StreamContent 和 MemoryStream 实例。

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


解决方案 2

另一种选择是将 Func<StreamContent> 委托传递给 MockClient 方法。

private void MockClient(HttpStatusCode status,Func<StreamContent> content = null)
{
   _client
      .Setup(m => m.Get(It.IsAny<string>(),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());
}

本文链接:https://www.f2er.com/1011.html

大家都在问