MimePartMatcher does not match multipart/form-data request body (possible bug?)
Hello, First of all, thank you for this library — it helps us a lot in our development work!
I'm using WireMock.Net.Testcontainers 1.15.0 for our tests and the sheyenrath/wiremock.net-alpine:1.15.0 Docker image in our development environment.
We want to define a stub endpoint that accepts a multipart/form-data HTTP request.
Unfortunately, we’re not able to create a request body matcher using MimePartMatcher in a JSON mapping file.
What Works
This body matcher works correctly:
"Body": {
"Matcher": {
"Name": "RegexMatcher",
"Pattern": ".+"
}
}
What Doesn’t Work
When using MimePartMatcher, even with very lenient matchers, the matcher always returns a 0.0 score for the body:
"Body": {
"Matcher": {
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "RegexMatcher",
"Pattern": ".*"
},
"ContentDispositionMatcher": {
"Name": "RegexMatcher",
"Pattern": ".*"
},
"ContentMatcher": {
"Name": "RegexMatcher",
"Pattern": ".*"
}
}
}
I also tried:
-
WildcardMatcherwith*for all matchers -
JsonMatcherwith the exact JSON sent -
JsonPartialMatcherusing regex (with"Regex": true) and.*for all JSON values - Setting
"ContentTypeMatcher"with"Name": "ContentTypeMatcher"and"Pattern": "application/json"
→ Always a 0.0 score for BodyMatcher (while PathMatcher, MethodMatcher, and HeadersMatcher all score 1.0).
Request Example
Here’s a sample curl request (exported from Postman):
curl -L -X POST "http://localhost:8080/my-stub-endpoint" \
-F "metadata=\"{\\\"ID\\\": \\\"9858013b-e020-4ef9-b8a8-0bebc740e6a7\\\", \\\"DATE\\\": \\\"2025-08-15T13:45:30.0000000Z\\\", \\\"NAME\\\": \\\"32c9a8dd-e214-4afb-9611-9cde81f827c6\\\", \\\"NUMBER\\\": 10}\";type=application/json"
Observation
From the /__admin/requests endpoint, here’s an example of the request body WireMock receives:
"Headers": {
"Accept": [
"*/*"
],
"Host": [
"localhost:8080"
],
"User-Agent": [
"curl/8.13.0"
],
"Content-Type": [
"multipart/form-data; boundary=------------------------woli8b80pw4vBJtNpAMOKS"
],
"Content-Length": [
"334"
]
},
"Body": "--------------------------woli8b80pw4vBJtNpAMOKS\r\n
Content-Disposition: form-data; name=\"metadata\"\r\n
Content-Type: application/json\r\n
\r\n
{\"ID\": \"9858013b-e020-4ef9-b8a8-0bebc740e6a7\", \"DATE\": \"2025-08-15T13:45:30.0000000Z\", \"NAME\": \"32c9a8dd-e214-4afb-9611-9cde81f827c6\", \"NUMBER\": 10}\r\n
--------------------------woli8b80pw4vBJtNpAMOKS--\r\n"
After digging a bit into the code, it seems that the issue comes from the use of the MimeKit library, which appears unable to parse this request body.
As a result, WireMock fails to extract the body parts, and the matcher score stays at its default value (0.0).
Possible Root Cause
It looks like the body content doesn’t include headers (such as Content-Type with a boundary) that would allow MimeKit to properly parse it into BodyParts.
In real HTTP requests, this header is not part of the body — it’s in the request headers — which might explain the mismatch compared to the examples in MimePartMatcherTests, where the content type is included in the body.
Question
Could this be a limitation or a bug in how MimePartMatcher / MimeKit handles real multipart/form-data requests?
Or is there a recommended approach to match multipart requests in JSON mappings?
Thank you in advance for your time and for maintaining this great library!
@BrunoBeraud I need to check.
@BrunoBeraud I did check the code and it's correct, in my unit tests I need to pass that text (including Content-Type as part of the body) to get a valid MimeMessage with 3 parts.
The real code will use the Header and prepend this to your body.
I think that your mapping json is wrong. A possible mapping json for checking 3 parts (same as in the unit test) can be like:
{
"Guid": "b9c82182-e469-41da-bcaf-b6e3157fefdb",
"UpdatedAt": "2023-07-24T18:12:55.564978Z",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/multipart",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matchers": [
{
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "text/plain",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "ExactMatcher",
"Pattern": "This is some plain text",
"IgnoreCase": false
},
"Name": "MimePartMatcher"
},
{
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "text/json",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "JsonMatcher",
"Pattern": {
"Key": "Value"
},
"IgnoreCase": true
},
"Name": "MimePartMatcher"
},
{
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "image/png",
"IgnoreCase": true
},
"ContentDispositionMatcher": {
"Name": "ExactMatcher",
"Pattern": "attachment; filename=\"image.png\"",
"IgnoreCase": true
},
"ContentTransferEncodingMatcher": {
"Name": "ExactMatcher",
"Pattern": "base64",
"IgnoreCase": true
},
"ContentMatcher": {
"Name": "ExactObjectMatcher",
"Pattern": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"
},
"Name": "MimePartMatcher"
}
],
"MatchOperator": "Or"
}
},
"Response": {
"BodyDestination": "SameAsSource",
"Body": "MultiPart is ok"
}
}
See also this extra unit test --> https://github.com/wiremock/WireMock.Net/pull/1389
Hello, I have tried your json multipart request mapping file with sheyenrath/wiremock.net-alpine:1.15.0 docker container.
I used this curl request:
curl -X POST http://localhost:8080/multipart \ -F 'plainText=This is some plain text;type=text/plain' \ -F 'jsonData={ "Key": "Value" };type=text/json' \ -F '[email protected];type=image/png'
The image is the one expecting in the ExactObjectMatcher base64 pattern
MatchDetails all 1.0 but BodyMatcher still 0.0...
@BrunoBeraud
I did not test the Docker image, but just local code. And the mapping in json is like:
{
"Guid": "b9c82182-e469-41da-bcaf-b6e3157fefdb",
"UpdatedAt": "2025-12-18T17:21:57.3879723Z",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/multipart",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matchers": [
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "text/plain",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "ExactMatcher",
"Pattern": "This is some plain text",
"IgnoreCase": false
}
},
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "text/json",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "JsonMatcher",
"Pattern": {
"Key": "Value"
},
"IgnoreCase": true,
"Regex": false
}
},
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "image/png",
"IgnoreCase": false
},
"ContentDispositionMatcher": {
"Name": "ExactMatcher",
"Pattern": "attachment; filename=\"image.png\"",
"IgnoreCase": false
},
"ContentTransferEncodingMatcher": {
"Name": "ExactMatcher",
"Pattern": "base64",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "ExactObjectMatcher",
"Pattern": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"
}
}
],
"MatchOperator": "Or"
}
},
"Response": {
"BodyDestination": "SameAsSource",
"Body": "MultiPart is ok"
}
}
The same mapping in code is:
var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain");
var textPlainContentMatcher = new ExactMatcher("This is some plain text");
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJsonContentTypeMatcher = new ContentTypeMatcher("text/json");
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png");
var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\"");
var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64");
var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"));
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher);
var matchers = new IMatcher[]
{
textPlainMatcher,
textJsonMatcher,
imagePngMatcher
};
server
.Given(Request.Create()
.WithPath("/multipart")
.UsingPost()
.WithMultiPart(matchers)
)
.WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb")
.RespondWith(Response.Create()
.WithBody("MultiPart is ok")
);
See also: https://github.com/wiremock/WireMock.Net/blob/mime-part-matcher/examples/WireMock.Net.Console.NET8/MainApp.cs#L559
The image.png is attached to this comment.
(It's a small red dot below...)
Running CURL on windows:
PS C:\temp> curl -X POST http://localhost:9091/multipart \ -F 'plainText=This is some plain text;type=text/plain' \ -F 'jsonData={ "Key": "Value" };type=text/json' \ -F '[email protected];type=image/png'
MultiPart is okcurl: (3) URL rejected: Bad hostname
curl: (3) URL rejected: Bad hostname
curl: (3) URL rejected: Bad hostname
PS C:\temp>
Logging:
18/12/2025 17:30:39 [DebugRequestResponse] : Admin[False] {
"Guid": "950acde0-f0eb-4810-ba07-022e7305c39e",
"Request": {
"ClientIP": "::1",
"DateTime": "2025-12-18T17:30:37.2241528Z",
"Path": "/multipart",
"AbsolutePath": "/multipart",
"Url": "http://localhost:9091/multipart",
"AbsoluteUrl": "http://localhost:9091/multipart",
"ProxyUrl": null,
"Query": {},
"Method": "POST",
"HttpVersion": "1.1",
"Headers": {
"Accept": [
"*/*"
],
"Host": [
"localhost:9091"
],
"User-Agent": [
"curl/8.16.0"
],
"Content-Type": [
"multipart/form-data; boundary=------------------------5kocnZUaqTqJbcOKOCaG3m"
],
"Content-Length": [
"591"
]
},
"Cookies": {},
"Body": null,
"BodyAsJson": null,
"BodyAsBytes": "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS01a29jblpVYXFUcUpiY09LT0NhRzNtDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InBsYWluVGV4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpUaGlzIGlzIHNvbWUgcGxhaW4gdGV4dA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS01a29jblpVYXFUcUpiY09LT0NhRzNtDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9Impzb25EYXRhIg0KQ29udGVudC1UeXBlOiB0ZXh0L2pzb24NCg0KeyAiS2V5IjogIlZhbHVlIiB9DQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTVrb2NuWlVhcVRxSmJjT0tPQ2FHM20NCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iaW1hZ2UiOyBmaWxlbmFtZT0iaW1hZ2UucG5nIg0KQ29udGVudC1UeXBlOiBpbWFnZS9wbmcNCg0KiVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCCDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTVrb2NuWlVhcVRxSmJjT0tPQ2FHM20tLQ0K",
"BodyEncoding": null,
"DetectedBodyType": "Bytes",
"DetectedBodyTypeFromContentType": "MultiPart"
},
"Response": {
"StatusCode": null,
"Headers": {},
"BodyDestination": "SameAsSource",
"Body": "MultiPart is ok",
"BodyAsJson": null,
"BodyAsBytes": null,
"BodyAsFile": null,
"BodyAsFileIsCached": null,
"BodyOriginal": null,
"BodyEncoding": {
"CodePage": 65001,
"EncodingName": "Unicode (UTF-8)",
"WebName": "utf-8"
},
"DetectedBodyType": 1,
"DetectedBodyTypeFromContentType": null,
"FaultType": null,
"FaultPercentage": null
},
"MappingGuid": "b9c82182-e469-41da-bcaf-b6e3157fefdb",
"MappingTitle": null,
"RequestMatchResult": {
"TotalScore": 3.0,
"TotalNumber": 3,
"IsPerfectMatch": true,
"AverageTotalScore": 1.0,
"MatchDetails": [
{
"Name": "PathMatcher",
"Score": 1.0
},
{
"Name": "MethodMatcher",
"Score": 1.0
},
{
"Name": "MultiPartMatcher",
"Score": 1.0
}
]
},
"PartialMappingGuid": "b9c82182-e469-41da-bcaf-b6e3157fefdb",
"PartialMappingTitle": null,
"PartialRequestMatchResult": {
"TotalScore": 3.0,
"TotalNumber": 3,
"IsPerfectMatch": true,
"AverageTotalScore": 1.0,
"MatchDetails": [
{
"Name": "PathMatcher",
"Score": 1.0
},
{
"Name": "MethodMatcher",
"Score": 1.0
},
{
"Name": "MultiPartMatcher",
"Score": 1.0
}
]
}
}