Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Mockly.ApiVerificationTests/ApprovedApi/net47.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ namespace Mockly.Http
public Mockly.Http.RequestMockBuilder Twice() { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, System.Threading.Tasks.Task<bool>> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, bool> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder WithBody(object body) { }
public Mockly.Http.RequestMockBuilder WithBody(string wildcardPattern) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingJson(string json) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingRegex(string regex) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace Mockly
public Mockly.RequestMockBuilder With(System.Func<Mockly.RequestInfo, System.Threading.Tasks.Task<bool>> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.RequestMockBuilder With(System.Func<Mockly.RequestInfo, bool> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.RequestMockBuilder WithAnyQuery() { }
public Mockly.RequestMockBuilder WithBody(object body) { }
public Mockly.RequestMockBuilder WithBody(string wildcardPattern) { }
public Mockly.RequestMockBuilder WithBodyMatchingJson(string json) { }
public Mockly.RequestMockBuilder WithBodyMatchingRegex(string regex) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ namespace Mockly
public Mockly.RequestMockBuilder With(System.Func<Mockly.RequestInfo, System.Threading.Tasks.Task<bool>> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.RequestMockBuilder With(System.Func<Mockly.RequestInfo, bool> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.RequestMockBuilder WithAnyQuery() { }
public Mockly.RequestMockBuilder WithBody(object body) { }
public Mockly.RequestMockBuilder WithBody(string wildcardPattern) { }
public Mockly.RequestMockBuilder WithBodyMatchingJson(string json) { }
public Mockly.RequestMockBuilder WithBodyMatchingRegex([System.Diagnostics.CodeAnalysis.StringSyntax("Regex")] string regex) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ namespace Mockly.Http
public Mockly.Http.RequestMockBuilder Twice() { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, System.Threading.Tasks.Task<bool>> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, bool> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder WithBody(object body) { }
public Mockly.Http.RequestMockBuilder WithBody(string wildcardPattern) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingJson(string json) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingRegex(string regex) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ namespace Mockly.Http
public Mockly.Http.RequestMockBuilder Twice() { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, System.Threading.Tasks.Task<bool>> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder With(System.Func<Mockly.Http.RequestInfo, bool> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { }
public Mockly.Http.RequestMockBuilder WithBody(object body) { }
public Mockly.Http.RequestMockBuilder WithBody(string wildcardPattern) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingJson(string json) { }
public Mockly.Http.RequestMockBuilder WithBodyMatchingRegex(string regex) { }
Expand Down
49 changes: 49 additions & 0 deletions Mockly.Specs/HttpMockSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,55 @@ await act.Should().ThrowAsync<RequestMatchingException>().WithMessage(
"Could not parse the request body as JSON*");
}

[Fact]
public async Task Can_match_the_body_against_a_serialized_object()
{
// Arrange
var mock = new HttpMock();

mock.ForPost()
.WithPath("/api/json")
.WithBody(new { name = "John", age = 30 })
.RespondsWithStatus(HttpStatusCode.NoContent);

var client = mock.GetClient();

// Act
var response = await client.PostAsync("https://localhost/api/json", new StringContent(
"""
{
"name" : "John",
"age" : 30
}

"""));

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
}

[Fact]
public async Task Will_report_the_expected_json_for_serialized_object()
{
// Arrange
var mock = new HttpMock();

mock.ForPost()
.WithPath("/api/json")
.WithBody(new { name = "John", age = 30 })
.RespondsWithStatus(HttpStatusCode.NoContent);

var client = mock.GetClient();

// Act
var action = async () => await client.PostAsync("https://localhost/api/json",
new StringContent("{\"name\": \"Jane\", \"age\": 25}"));

// Assert
await action.Should().ThrowAsync<UnexpectedRequestException>()
.WithMessage("*body matches JSON*\"name\"*\"John\"*");
}

[Fact]
public async Task Can_match_body_against_a_regex_pattern()
{
Expand Down
21 changes: 21 additions & 0 deletions Mockly/RequestMockBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ public RequestMockBuilder WithBodyMatchingJson(string json)
}, $"body matches JSON {json}");
}

/// <summary>
/// Configures the request mock to match requests whose body contains the JSON equivalent to the specified object,
/// serialized using <see cref="JsonSerializer"/>, ignoring differences in whitespace and layout.
/// </summary>
/// <param name="body">The object to serialize to JSON and compare against the request body.</param>
/// <remarks>
/// The <paramref name="body"/> is serialized using <see cref="JsonSerializer.Serialize(object?, System.Type, JsonSerializerOptions?)"/>
/// with the default <see cref="JsonSerializerOptions"/>. The comparison is then performed by comparing the serialized JSON
/// with the request body using JSON equivalence, ignoring differences in whitespace and layout.
/// </remarks>
public RequestMockBuilder WithBody(object body)
Comment thread
dennisdoomen marked this conversation as resolved.
{
if (body is null)
{
throw new ArgumentNullException(nameof(body));
}

var json = JsonSerializer.Serialize(body);
return WithBodyMatchingJson(json);
}

/// <summary>
/// Configures the request mock to match requests whose body content satisfies the specified wildcard pattern.
/// </summary>
Expand Down
19 changes: 18 additions & 1 deletion website/docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mock.ForPost()

### JSON Equivalence

Layout and whitespace independent:
Layout and whitespace independent, using a raw JSON string:

```csharp
mock.ForPost()
Expand All @@ -71,6 +71,23 @@ mock.ForPost()
If the body cannot be parsed as JSON for `WithBodyMatchingJson`, a `RequestMatchingException` is thrown.
:::

### Object Serialized to JSON

Pass an object directly and let Mockly serialize it to JSON for matching. This is useful when you have a strongly-typed request body:

```csharp
mock.ForPatch()
.WithPath("/api/relationships/42")
.WithBody(new
{
EntityKey = "TheRuleKey",
RepresentativeId = "abc123"
})
.RespondsWithStatus(HttpStatusCode.NoContent);
```

The object is serialized using `JsonSerializer` with default options and compared to the request body using JSON equivalence, ignoring differences in whitespace and layout.

### Regular Expression

```csharp
Expand Down