Skip to content

Verifying Requests

github-actions[bot] edited this page Oct 5, 2026 · 5 revisions

Verifying Requests

The server records every request it receives, matched or not. Use this to check that your client sent the right thing. server.Should() starts a chain of assertions; each one checks right away and throws MockVerificationException when it fails.

Checking requests

using var server = new MockServer(new TcpServer(0));
server.Mock.Send("LOGIN alice").Receive("OK");
server.Mock.Send("LIST").Receive("a,b,c");
server.Start();

using (var client = await TcpTestClient.ConnectAsync(server.Port))
{
    await client.SendAndReceiveAsync("LOGIN alice");
    await client.SendAndReceiveAsync("LIST");
    await client.SendAndReceiveAsync("LIST");
}

server.Should().HaveReceived("LOGIN alice");                       // at least once
server.Should().HaveReceived("LIST", Times.Exactly(2));
server.Should().NotHaveReceived("LOGOUT");
server.Should().HaveReceived(r => r.BodyString.StartsWith("LOGIN"), Times.Once());

HaveReceived and NotHaveReceived accept:

  • Text: HaveReceived("LIST"), an exact match.
  • Bytes: HaveReceived(new byte[] { 0x01, 0x02 }), an exact match.
  • A predicate: HaveReceived(r => ...), which gets each ReceivedRequest.

Without a Times, HaveReceived checks that the request arrived at least once.

Chain several checks with And:

server.Should().HaveReceived("LOGIN bob", Times.Once())
    .And.HaveReceived("LIST")
    .And.NotHaveReceived("DELETE")
    .And.HaveReceivedInOrder("LOGIN bob", "LIST")
    .And.HaveNoUnmatchedRequests()
    .And.HaveAcceptedConnections(Times.Once());

Times

server.Should().HaveReceived("x", Times.Exactly(2));
server.Should().HaveReceived("x", Times.AtLeast(1));
server.Should().HaveReceived("x", Times.AtMost(3));
server.Should().HaveReceived("x", Times.Between(1, 2));
server.Should().HaveReceived("x", Times.AtLeastOnce());
server.Should().NotHaveReceived("y");
Times Accepts
Times.Never() 0
Times.Once() exactly 1
Times.AtLeastOnce() 1 or more
Times.Exactly(n) exactly n
Times.AtLeast(n) n or more
Times.AtMost(n) 0 to n
Times.Between(min, max) min to max, inclusive

If you also use Moq, which has its own Times, write Rony.Net.Times or add an alias: using Times = Rony.Net.Times;

Failure messages

A failed check throws MockVerificationException with everything the server received, which usually shows the problem right away:

server.Mock.Send("PING").Receive("PONG");
server.Mock.Match("PING");
server.Mock.Match("PNIG");

server.Should().HaveReceived("PING", Times.Exactly(2));
Rony.Net.MockVerificationException:
Expected request "PING" exactly 2 times, but it was received 1 time.
Received requests:
  1. "PING"
  2. "PNIG" (unmatched)

Binary requests are shown as hex, for example 0xFF 0x01.

Strict mode

HaveNoUnmatchedRequests() fails if any request had no configured response. It catches requests your client sent that you didn't expect:

server.Should().HaveNoUnmatchedRequests();
Rony.Net.MockVerificationException:
1 request had no configured response:
  "DELETE everything"

A good habit is to call it at the end of a test, or in your test class's Dispose. The test framework packages do that for you with VerifyAllRequestsMatchedAfterTest = true. A Send("") default response matches everything, so with one configured, strict mode always passes. Requests answered by OnUnmatched() still count as unmatched.

Fail fast on unexpected requests

Strict mode reports unexpected requests at the end of the test. With FailOnUnmatched, the test fails as soon as the server receives one: every following check (Should()... or Verify...) throws, and so does every WaitFor... call, including one that is already waiting. A typo then fails the test right away instead of after a long wait:

server.Mock.FailOnUnmatched = true;
server.Mock.Send("HEARTBEAT").NoReply();
server.Start();

using var client = await TcpTestClient.ConnectAsync(server.Port);
await client.SendAsync("HEARTBAET");   // the bug under test

// Fails as soon as the typo arrives, instead of after 30 seconds.
var error = await Assert.ThrowsAsync<MockVerificationException>(
    () => server.Mock.WaitForRequestAsync("HEARTBEAT", TimeSpan.FromSeconds(30)));

Order

HaveReceivedInOrder checks that requests arrived in a given order. Other requests may come before, after or in between:

// The client sent LOGIN bob, NOOP, LIST, QUIT
server.Should().HaveReceivedInOrder("LOGIN bob", "LIST", "QUIT");
server.Should().HaveReceivedInOrder(r => r.BodyString.StartsWith("LOGIN"), r => r.BodyString == "QUIT");

It takes text, bytes or predicates. When the order is wrong, the message says which request is missing:

Rony.Net.MockVerificationException:
Expected requests in order: "LOGIN bob", "LIST", but "LIST" was not received after "LOGIN bob".
Received requests:
  1. "LIST" (unmatched)
  2. "LOGIN bob" (unmatched)

To check the order on one connection only, use connection.Should().

Assertions on one connection

connection.Should() has the same HaveReceived, NotHaveReceived and HaveReceivedInOrder assertions, but only requests received on that connection count. Use it when several clients talk to the server at once (connections are TCP only):

var connection = await server.WaitForConnectionAsync();
connection.Should().HaveReceived("LOGIN bob", Times.Once())
    .And.HaveReceivedInOrder("LOGIN bob", "LIST")
    .And.NotHaveReceived("QUIT");

The failure messages name the connection ("... on connection #1") and list only its requests. BeInState(state) checks the connection's scenario state, and BeOpen() and BeClosed() check whether it is still open; wait with connection.WaitForCloseAsync() first when the client may still be disconnecting. HaveUsedTls(...), HaveServerName(...) and HavePresentedClientCertificate(...) check the TLS details. With FailOnUnmatched the check stays server-wide: an unmatched request on any connection makes the assertion throw.

All assertions

Assertion Classic method
HaveReceived(request or predicate[, times]) server.Mock.Verify(...); at least once without times
NotHaveReceived(request or predicate) server.Mock.Verify(..., Times.Never())
HaveReceivedInOrder(...) server.Mock.VerifyInOrder(...)
HaveNoUnmatchedRequests() server.Mock.VerifyAllRequestsMatched()
HaveAcceptedConnections(times) server.VerifyConnections(times) (TCP)
HaveNoOpenConnections() Assert.Empty(server.OpenConnections) (TCP)
BeInState(state) Assert.Equal(state, server.Mock.State) (scenarios)

The classic methods do the same checks with the same messages, if you prefer that style or are upgrading from 1.0. Should() is a method of MockServer, so it works next to FluentAssertions or Shouldly without conflicts.

Inspecting requests

server.ReceivedRequests, or server.Mock.ReceivedRequests, lists every request, oldest first:

IReadOnlyList<ReceivedRequest> requests = server.ReceivedRequests;
Assert.Equal(new[] { "first", "second" }, requests.Select(r => r.BodyString));
Assert.All(requests, r => Assert.True(r.Matched));
Assert.All(requests, r => Assert.NotNull(r.RemoteEndPoint));
Assert.True(requests[0].Timestamp <= requests[1].Timestamp);
ReceivedRequest
Body The request bytes, with framing removed
BodyString The request as UTF-8 text
RemoteEndPoint The client's address and port
Timestamp When the server received it
Matched Whether a configured response handled it
ConnectionId The connection it arrived on (TCP); null for UDP and Match(...)
ToJson() The request as one line of JSON, see below

server.Mock.UnmatchedRequests lists only the requests that had no response.

Limiting and journaling requests

A server that runs for a long time keeps every request. Mock.MaxReceivedRequests caps that number (0, the default, is unlimited): when it is exceeded the oldest requests are dropped when the next one is recorded, and ReceivedRequests, UnmatchedRequests, Verify..., Should() and the WaitFor... methods see only the kept ones, so WaitForRequestsAsync(count) with a count above the cap can never complete.

server.RequestReceived is raised for every request after it was matched and recorded and before its response is sent. request.ToJson() is the request as one line of JSON, for a journal that is not limited by the cap:

server.Mock.MaxReceivedRequests = 1000;
server.RequestReceived += (sender, request) => journal.WriteLine(request.ToJson());

Requests of one connection are raised in order; connections may raise concurrently, so the handler must be thread-safe. An exception thrown by a handler is logged and ignored. The line looks like {"time":"2026-10-04T12:34:56.789+02:00","connection":1,"remote":"127.0.0.1:50123","matched":true,"text":"PING"}; see Journal for the properties.

Starting over

ClearReceivedRequests() forgets what was received but keeps the configuration, which is useful between the steps of a longer test:

server.Mock.ClearReceivedRequests();

server.Should().NotHaveReceived("ping");

Reset() clears both the recorded requests and the configuration.

Timing

An assertion checks what has been received so far. If your client sends in the background, the request might not have arrived yet when you verify. In that case, wait for it first.

Runnable code: VerificationSamples.cs

Clone this wiki locally