Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support MultipartContent in HttpCallAssertion #634

Open
cremor opened this issue Jun 21, 2021 · 4 comments
Open

Support MultipartContent in HttpCallAssertion #634

cremor opened this issue Jun 21, 2021 · 4 comments

Comments

@cremor
Copy link

cremor commented Jun 21, 2021

It just took some time until I figured out why my unit test that that uses WithRequestBody always fails for code that uses PostMultipartAsync. I had to check the Flurl code and finally the comment on FlurlCall.RequestBody explained it ("Available ONLY if HttpRequestMessage.Content is a Flurl.Http.Content.CapturedStringContent."). So MultipartContent is simply not supported here.

It would be nice if that could be changed. But TBH I'm not sure how the API should look like. Just supporting it in WithRequestBody (that accepts a string pattern) would be a great start. But maybe a separate WithRequestMultipart (like WithRequestUrlEncoded) would be better. That could then somehow allow asserting the parts in the CapturedMultipartContent.

Here is how I do this now with an extension method:

public static HttpCallAssertion WithRequestMultipart(this HttpCallAssertion assertion, Func<CapturedMultipartContent, bool> predicate) {
   return assertion.With(x => predicate(x.HttpRequestMessage.Content as CapturedMultipartContent), "CapturedMultipartContent body");
}

Usage example:

_httpTest
   .ShouldHaveCalled("...")
   .WithRequestMultipart(x =>
      x.Parts.Any(x => x.Headers.ContentDisposition.Name == "SomeParameter") &&
      x.Parts.Any(x => ((CapturedStringContent)x).Content.Contains("SomeValue"))
   )
   .Times(1);

This works, but an API that requires less code for the usage would be even better. E.g. something that allows checking all headers and content with pattern support.

I also suggest a quick win until this is implemented: The fact that MultipartContent is not supported in WithRequestBody should be documented. Either directly on the method, or on https://flurl.dev/docs/testable-http/ (or on both).

@tmenier
Copy link
Owner

tmenier commented Jul 9, 2021

Thanks for the suggestion, it's a good idea and I'll add it to the backlog.

@bgmulinari
Copy link

bgmulinari commented Aug 16, 2023

Hi, @tmenier. Any update on this?

I came across this issue while trying to write a test for a Multipart request, and unfortunately for me, even @cremor's extension method does not work, since the Parts property is empty for some reason.

image

EDIT: I should add that I'm on version 4.0.0-pre3.

@bgmulinari
Copy link

For now, I had to do some less than ideal workarounds in order to be able to test my multipart requests:

HttpTest.Settings.HttpClientFactory = 
    new CustomHttpClientFactory(new CustomDelegatingHandler(HttpTest.Settings.HttpClientFactory.CreateMessageHandler()));

// Custom classes definitions
private class CustomDelegatingHandler : DelegatingHandler
{
    public CustomDelegatingHandler(HttpMessageHandler innerHandler) => this.InnerHandler = innerHandler;

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var stringContent = request.Content is CapturedMultipartContent ? await request.Content.ReadAsStringAsync() : null;
        var result = await base.SendAsync(request, cancellationToken);
        request.Content = stringContent != null ? new CapturedStringContent(stringContent) : request.Content;
        return result;
    }
}

private class CustomHttpClientFactory : DefaultHttpClientFactory
{
    private readonly CustomDelegatingHandler _interceptor;

    public CustomHttpClientFactory(CustomDelegatingHandler interceptor) => _interceptor = interceptor;

    public override HttpMessageHandler CreateMessageHandler() => _interceptor;
}

This overwrites the Content of my request after it's sent as a CapturedStringContent in case it's a CapturedMultipartContent.

This allows me to test my requests as shown below. Another workaround is that I need to use FileSystemName.MatchesSimpleExpression instead of WithRequestBody, because for some reason the test will always fail otherwise.

In this case, expectedRequestBody is a huge string containing the multipart content that is expected, with several * as a wildcard where applicable.

HttpTest.ShouldHaveCalled(SLConnectionV1.ServiceLayerRoot.AppendPathSegment("$batch"))
    .WithVerb(HttpMethod.Post)
    .With(call => FileSystemName.MatchesSimpleExpression(expectedRequestBody, call.RequestBody))
    .Times(1);

Ugly, I know, but as stated by @cremor, the documentation for tests lacks any mention of multipart requests, so I'm not sure if testing multipart requests is even possible natively.

@tmenier
Copy link
Owner

tmenier commented Sep 11, 2023

@bgmulinari No updates at this point. I've taken a long hiatus and planning on saving non-breaking enhancements in the backlog for future minor version upgrades in order to get 4.0 out the door sooner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants