Skip to main content

Introduction

Mockly is a powerful and flexible HTTP mocking library for .NET that makes it easy to test code that depends on HttpClient. It provides a fluent API for configuring HTTP request mocks, capturing request details, and asserting on HTTP interactions in your tests.

Mockly Logo

Platform Support

The library supports:

  • .NET Framework 4.7.2 and higher
  • .NET 8.0 and higher
  • FluentAssertions 7.x and 8.x integration for expressive test assertions

What makes Mockly special?

Unlike other HTTP mocking libraries, Mockly offers:

  • Fluent, intuitive API - Chain method calls to build complex mocking scenarios with ease
  • Wildcard pattern matching - Match URLs using wildcards (*) in paths and query strings
  • Custom matchers - Use predicates for advanced request matching logic
  • Request capture & inspection - Automatically capture all requests with full metadata (headers, body, timestamp)
  • Powerful assertions - Built-in FluentAssertions extensions for verifying HTTP behavior
  • Diagnostic support - Detailed error messages when unexpected requests occur
  • Extensibility - Design allows for custom response generators and matchers
  • Zero configuration - Works out of the box with sensible defaults
  • Performance optimized - Regex patterns are cached for efficient matching
  • Invocation limits - Restrict how many times a mock can respond using Once(), Twice(), or Times(n)

Who created this?

Mockly is created and maintained by Dennis Doomen, also the creator of:

It's designed to work seamlessly with modern .NET testing practices and integrates naturally with FluentAssertions for expressive test assertions.

Key Features

🎯 Fluent Request Matching

mock.ForGet().WithPath("/api/users/*").RespondsWithJsonContent(user);
mock.ForPost().WithPath("/api/data").WithQuery("?filter=*").RespondsWithStatus(HttpStatusCode.Created);

📃 Clear Reporting

When an unexpected request occurs and there are configured mocks, Mockly helps you diagnose by reporting the closest matching mock (method, scheme/host/path/query) so you can quickly see what to adjust in your setup.

Unexpected request to:
GET http://localhost/fnv_collectiveschemes(111)

Closest matching mock:
GET https://*/fnv_collectiveschemes(123*)

Registered mocks:
- GET https://*/fnv_collectiveschemes
- POST https://*/fnv_collectiveschemes
- GET https://*/fnv_collectiveschemes(123*)
- GET https://*/fnv_collectiveschemes(123*) (1 custom matcher(s)) where (request => request.Uri?.Query == "?$count=1")
- GET https://*/fnv_collectiveschemes(456)

🔍 Request Capture & Inspection

var patches = new RequestCollection();
mock.ForPatch().WithPath("/api/update").CollectingRequestIn(patches);

// After test execution
patches.Count.Should().Be(3);
patches.First().Path.Should().Contain("/api/update");

✅ Powerful Assertions

mock.Should().HaveAllRequestsCalled();
mock.Requests.Should().NotBeEmpty();
mock.Requests.Should().NotContainUnexpectedCalls();

// Assert JSON-equivalence using a JSON string (ignores formatting/ordering)
mock.Requests.Should().ContainRequest()
.WithBodyMatchingJson("{ \"id\": 1, \"name\": \"x\" }");

// Assert the body deserializes and is equivalent to an object graph
var expected = new { id = 1, name = "x" };

mock.Requests.Should().ContainRequestForUrl("http://localhost:7021/api/*")
.WithBodyEquivalentTo(expected);

🎨 Multiple Response Types

  • JSON content with automatic serialization
  • Test data builder integration via IResponseBuilder<T>
  • Raw string content
  • Custom HTTP status codes
  • Custom response generators
  • OData support

🛡️ Fail-Fast Testing

mock.FailOnUnexpectedCalls = true; // Default behavior
// Throws UnexpectedRequestException if an unmocked request is made