Compare commits

...

7 Commits

Author SHA1 Message Date
Stef Heyenrath
197a211a52 TestcontainersTests 2025-12-13 11:48:48 +01:00
Stef Heyenrath
3cfeec6035 1.19.0 2025-12-12 11:16:38 +01:00
Stef Heyenrath
b57d5e7548 WireMockContainerBuilder: allow only docker images named wiremock (#1392) 2025-12-11 22:21:39 +01:00
Stef Heyenrath
36b89afce5 fix CI link in Readme 2025-12-11 11:25:28 +01:00
Stef Heyenrath
e2acac55a4 Update WireMockContainerBuilder (WithImage and WithCustomImage) (#1391)
* Update WireMockContainerBuilder (WithImage and WithCustomImage)

* .
2025-12-11 10:55:31 +01:00
Stef Heyenrath
ceabd27ce0 1.18.0 2025-12-09 18:28:28 +01:00
Stef Heyenrath
f8e2c7ee90 Add WithBodyAsType to RequestMatcher (#1388)
* Add WithBody<T>

* .

* t

* t2
2025-12-08 19:15:14 +01:00
13 changed files with 186 additions and 27 deletions

View File

@@ -1,3 +1,11 @@
# 1.19.0 (12 December 2025)
- [#1391](https://github.com/wiremock/WireMock.Net/pull/1391) - Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature] contributed by [StefH](https://github.com/StefH)
- [#1392](https://github.com/wiremock/WireMock.Net/pull/1392) - WireMockContainerBuilder: allow all docker images named wiremock [feature] contributed by [StefH](https://github.com/StefH)
- [#1390](https://github.com/wiremock/WireMock.Net/issues/1390) - Unable to build WireMockContainerBuilder with custom image [feature]
# 1.18.0 (09 December 2025)
- [#1388](https://github.com/wiremock/WireMock.Net/pull/1388) - Add WithBodyAsType to RequestMatcher [feature] contributed by [StefH](https://github.com/StefH)
# 1.17.0 (07 December 2025)
- [#1383](https://github.com/wiremock/WireMock.Net/pull/1383) - Aspire: Add WithProtoDefinition to support proto definition at server level [feature] contributed by [StefH](https://github.com/StefH)
- [#1386](https://github.com/wiremock/WireMock.Net/pull/1386) - Fix random delay in mapping json file [bug] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.17.0</VersionPrefix>
<VersionPrefix>1.19.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.17.0
SET version=1.19.0
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -1,11 +1,6 @@
# 1.17.0 (07 December 2025)
- #1383 Aspire: Add WithProtoDefinition to support proto definition at server level [feature]
- #1386 Fix random delay in mapping json file [bug]
- #812 Wiki page for using WireMock.Net with appium for mobile automation testing [wontfix]
- #1274 .WithMappings to mount volume is not working for GRPC [bug]
- #1381 Downstream dependencies missing after 1.16.0 release [bug]
- #1382 Does Aspire support enabling HTTP/2? [feature]
- #1385 Do delays and probabilities show in saved static mappings? [bug]
- #1387 Tests failing with TaskCanceledException on Windows Server 2025 Build 7171 [bug]
# 1.19.0 (12 December 2025)
- #1391 Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature]
- #1392 WireMockContainerBuilder: allow all docker images named wiremock [feature]
- #1390 Unable to build WireMockContainerBuilder with custom image [feature]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -33,7 +33,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/wiremock/WireMock.Net/issues) |
| | |
| ***Quality*** | &nbsp; |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=7) |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|

View File

@@ -0,0 +1,60 @@
// Copyright © WireMock.Net
using System;
using Newtonsoft.Json.Linq;
using Stef.Validation;
namespace WireMock.Matchers.Request;
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher<T> : IRequestMatcher
{
/// <summary>
/// The body data function for type T
/// </summary>
public Func<T?, bool>? Func { get; }
/// <summary>
/// The <see cref="MatchOperator"/>
/// </summary>
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<T?, bool> func)
{
Func = Guard.NotNull(func);
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
{
if (Func != null)
{
if (requestMessage.BodyData?.BodyAsJson is JObject jsonObject)
{
try
{
var bodyAsT = jsonObject.ToObject<T>();
return MatchScores.ToScore(Func(bodyAsT));
}
catch (Exception ex)
{
return new MatchResult(ex);
}
}
}
return default;
}
}

View File

@@ -34,13 +34,6 @@ public partial class Request
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
return WithBody([matcher]);
}
/// <inheritdoc />
public IRequestBuilder WithBody(IMatcher matcher)
{
@@ -98,4 +91,20 @@ public partial class Request
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
return WithBody([matcher]);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func)
{
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher<T>(func));
return this;
}
}

View File

@@ -29,7 +29,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder Create()
{
return new Request(new List<IRequestMatcher>());
return new Request([]);
}
/// <summary>

View File

@@ -80,6 +80,14 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<object?, bool> func);
/// <summary>
/// WithBody: func (type)
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func);
/// <summary>
/// WithBody: func (BodyData object)
/// </summary>

View File

@@ -62,6 +62,29 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
return WithImage(OSPlatform.Windows);
}
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public new WireMockContainerBuilder WithImage(string image)
{
return WithCustomImage(image);
}
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder WithCustomImage(string image)
{
_imageOS ??= TestcontainersUtils.GetImageOSAsync.Value.GetAwaiter().GetResult();
return base.WithImage(image);
}
/// <summary>
/// Set the admin username and password for the container (basic authentication).
/// </summary>
@@ -207,9 +230,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
// In case the _imageOS is not set, determine it from the Image FullName.
if (_imageOS == null)
{
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock.net", StringComparison.OrdinalIgnoreCase) < 0)
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock", StringComparison.OrdinalIgnoreCase) < 0)
{
throw new InvalidOperationException();
throw new InvalidOperationException("It's only possible to use a wiremock docker image.");
}
_imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;

View File

@@ -1,11 +1,12 @@
// Copyright © WireMock.Net
using System;
using FluentAssertions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
using WireMock.Matchers.Request;
@@ -72,15 +73,17 @@ public class RequestBuilderWithBodyTests
}
[Fact]
public void Request_WithBody_FuncJson()
public void Request_WithBody_FuncObject()
{
// Assign
var requestBuilder = Request.Create().UsingAnyMethod().WithBody(b => b != null);
var requestBuilder = Request.Create()
.UsingAnyMethod()
.WithBody(b => b != null);
// Act
var body = new BodyData
{
BodyAsJson = 123,
BodyAsJson = JObject.Parse("""{ "X": 123, "Y": "a" }"""),
DetectedBodyType = BodyType.Json
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
@@ -90,6 +93,57 @@ public class RequestBuilderWithBodyTests
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0);
}
[Theory]
[InlineData("""{ "X": 123, "Y": "a" }""", 1.0)]
[InlineData("""{ "X": 123, "Y": "b" }""", 0.0)]
public void Request_WithBodyAsType_Func(string json, double expected)
{
// Assign
var requestBuilder = Request.Create()
.UsingAnyMethod()
.WithBodyAsType<FuncType>(ft => ft != null && ft.X == 123 && ft.Y == "a");
// Act
var body = new BodyData
{
BodyAsJson = JObject.Parse(json),
DetectedBodyType = BodyType.Json
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
// Assert
var requestMatchResult = new RequestMatchResult();
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(expected);
}
[Fact]
public void Request_WithBodyAsType_Func_IncorrectType()
{
// Assign
var requestBuilder = Request.Create()
.UsingAnyMethod()
.WithBodyAsType<Version>(ft => ft != null);
// Act
var body = new BodyData
{
BodyAsJson = JObject.Parse("""{ "X": 123, "Y": "a" }"""),
DetectedBodyType = BodyType.Json
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, body);
// Assert
var requestMatchResult = new RequestMatchResult();
Check.That(requestBuilder.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0);
}
private class FuncType
{
public int X { get; set; } = 42;
public string Y { get; set; } = string.Empty;
}
[Fact]
public void Request_WithBody_FuncFormUrlEncoded()
{

View File

@@ -205,6 +205,8 @@ public partial class TestcontainersTests
var result = await httpClient.PostAsync("/__admin/mappings", new StringContent(mappingsJson, Encoding.UTF8, WireMockConstants.ContentTypeJson));
result.EnsureSuccessStatusCode();
await Task.Delay(1000);
}
private static async Task<HelloReply> When_GrpcClient_Calls_SayHelloAsync(WireMockContainer wireMockContainer)

View File

@@ -92,7 +92,7 @@ public partial class TestcontainersTests(ITestOutputHelper testOutputHelper)
}
else
{
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net");
wireMockContainerBuilder = wireMockContainerBuilder.WithImage("sheyenrath/wiremock.net-alpine");
}
var wireMockContainer = wireMockContainerBuilder.Build();