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.
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(), orTimes(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