I'm using Flurl Http to make http requests. In the unit tests, I'm trying to verify that the expected content was passed to the sender. I'm trying it like:
httpTest.ShouldHaveCalled(url)
.WithVerb(HttpMethod.Post)
.WithContentType(contentType)
.With(w => w.Request.Content.ReadAsStringAsync().Result == content)
.Times(1);
However, this fails with System.ObjectDisposedException Cannot access a disposed object. Object name: 'System.Net.Http.StringContent'.
It looks like Flurl is disposing the request body content before the verification is done in the test. How can I capture the request body for verification?
EDIT (A fully reproducible example):
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Autofac.Extras.Moq;
using Flurl.Http;
using Flurl.Http.Testing;
using Xunit;
namespace XUnitTestProject1
{
public class MyClassTest : IDisposable
{
private readonly AutoMock container;
private readonly HttpTest client;
public MyClassTest()
{
this.container = AutoMock.GetLoose();
this.client = new HttpTest();
}
[Fact]
public async Task SendAsync_ValidateRequestBody()
{
const string url = "http://www.example.com";
const string content = "Hello, world";
var sut = this.container.Create<MyClass>();
await sut.SendAsync(url, content);
this.client.ShouldHaveCalled(url)
.With(w => w.Request.Content.ReadAsStringAsync().Result == content);
}
public void Dispose()
{
this.container?.Dispose();
this.client?.Dispose();
}
}
public class MyClass
{
public virtual async Task SendAsync(string url, string content)
{
await url.PostAsync(new StringContent(content, Encoding.UTF8, "text/plain"));
}
}
}
In most cases (see edit below), Flurl has captured it, you just have to access it differently.
In your example, w.Request
is a "raw" HttpRequestMessage, from the HttpClient
stack, that Flurl exposes so you can get under the hood if you need to. HttpRequestMessage.Content
is a read-once, forward-only stream that has already been read and disposed by the time you're accessing it.
To assert the captured string body, you would typically just do this instead:
httpTest.ShouldHaveCalled(url)
...
.WithRequestBody(content)
EDIT
As you noted, this doesn't work based on how you're using Flurl. The string contained by StringContent
is effectively write-only, i.e. no property exposes it for reading. This is the purpose of Flurl's CapturedStringContent
. If you use that type as a direct replacement for StringContent
, RequestBody
will be available in your test.
The reason this isn't very well covered in the docs is because if you do things "the Flurl way", you're not explicitly creating content objects in the first place. PostStringAsync
and PostJsonAsync
are the far more common ways to send a POST request, and both are implemented using CapturedStringContent
. Use one of those methods if you can, or use PostAsync(new CapturedStringContent(...))
if you need to get at the lower-level content object for some reason.