Repository navigation
Request Matching
Send("text") and Send(bytes) match a request with exactly those bytes:
server.Mock.Send("LIST").Receive("a,b,c");
server.Mock.Send(new byte[] { 0x01, 0x02 }).Receive(new byte[] { 0x06 });With framing, the delimiter or length prefix is removed before matching.
For requests with changing parts, such as IDs, timestamps or credentials:
using var server = new MockServer(new TcpServer(0));
server.Mock.Send(new Regex(@"^LOGIN \w+ \w+$")).Receive("OK");
server.Start();
using var client = await TcpTestClient.ConnectAsync(server.Port);
Assert.Equal("OK", await client.SendAndReceiveAsync("LOGIN alice secret"));The regular expression is applied to the request decoded as UTF-8 text.
For anything a regex can't express, pass a function that decides whether a request matches. SendMatching gets the
request as text, and SendMatchingBytes gets the raw bytes:
server.Mock.SendMatching(text => text.StartsWith("GET ")).Receive("200 OK");
server.Mock.SendMatchingBytes(bytes => bytes.Length > 0 && bytes[0] == 0xFF).Receive(new byte[] { 0x00 });A predicate that throws, for example bytes[10] on a shorter request, simply doesn't match.
SendJson matches requests that are valid JSON and satisfy a function on the parsed document. JsonData is read-only and
indexing never throws, so a missing field is just Undefined:
server.Mock.SendJson(j => j["type"].AsString() == "login").Receive("{\"ok\":true}");
server.Mock.SendJson(j => j["items"].Count > 2 && j["user"]["roles"][0].AsString() == "admin").Receive("big admin order");
Assert.Equal("{\"ok\":true}", server.Mock.Match("{\"type\":\"login\",\"user\":\"bob\"}").GetString());
Assert.Empty(server.Mock.Match("not json")); // not JSON: no match, and no error is loggedA request that isn't valid JSON doesn't match and the function isn't called; a function that throws doesn't match either.
A JSON rule is a predicate rule, so it has the same place as SendMatching in which response wins.
There is no ReceiveJson: to build a response from the request, parse it yourself:
server.Mock.SendJson(j => j["id"].Exists)
.Receive(request => "{\"echo\":" + JsonData.Parse(request)["id"] + "}");
Assert.Equal("{\"echo\":42}", server.Mock.Match("{\"id\":42}").GetString());JsonData.Parse(text) throws FormatException for invalid JSON, and TryParse returns false.
Combine a pattern with a response function:
// The client sends "ORDER <id>" and expects "ACK <id>" back.
server.Mock.Send(new Regex(@"^ORDER \d+$")).Receive(request => "ACK " + request.Split(' ')[1]);
Assert.Equal("ACK 1001", await client.SendAndReceiveAsync("ORDER 1001"));
Assert.Equal("ACK 1002", await client.SendAndReceiveAsync("ORDER 1002"));ReceiveMatch (and ThenMatch in a sequence) passes the regular expression Match, so you can use
groups. They only exist after Send(Regex); on any other rule they throw InvalidOperationException. Receive(...) was
deliberately not extended with a Func<Match, string> overload, because Receive(x => "pong") would become ambiguous.
server.Mock.Send(new Regex(@"^HELLO (\w+)$")).ReceiveMatch(m => $"HI {m.Groups[1].Value}");
Assert.Equal("HI bob", server.Mock.Match("HELLO bob").GetString());When several configurations could match a request, the server picks the first that applies in this order:
-
Exact request:
Send("ABC")orSend(bytes). -
Patterns and predicates:
Send(Regex),SendMatching,SendMatchingBytes,SendJson, in the order you added them. -
Any request:
Send("").
server.Mock.Send("").Receive("any"); // 3. fallback
server.Mock.SendMatching(text => text.StartsWith("A")).Receive("A*"); // 2. patterns, in the order added
server.Mock.Send("ABC").Receive("exact"); // 1. exact request
Assert.Equal("exact", server.Mock.Match("ABC").GetString());
Assert.Equal("A*", server.Mock.Match("AXY").GetString());
Assert.Equal("any", server.Mock.Match("XYZ").GetString());Because patterns are tried in order, add the more specific ones first:
server.Mock.Send(new Regex("^GET /admin")).Receive("403 Forbidden");
server.Mock.Send(new Regex("^GET ")).Receive("200 OK");With scenario states, a rule for the current state wins over a rule without a state at each of these levels.
A request that no rule matches is recorded as unmatched. By default the server sends nothing back (UDP: an empty datagram) and closes the connection:
server.Mock.Send("PING").Receive("PONG");
// client sends "PNIG" → the connection is closedOnUnmatched() chooses a different reaction, with everything a response can do:
server.Mock.OnUnmatched().Receive(text => $"ERR unknown command '{text}'"); // answer, keep the connection
server.Mock.OnUnmatched().NoReply(); // ignore, keep the connection
server.Mock.OnUnmatched().Receive("ERR").AndDisconnect(); // answer, then hang upThe difference from a Send("") default: requests answered by OnUnmatched() still count as unmatched, so
strict mode and FailOnUnmatched
still report them. Use Send("") when "anything else" is a normal part of the protocol, and OnUnmatched() when it means
the client did something wrong.
server.Mock.Send("PING").Receive("PONG");
server.Mock.OnUnmatched().Receive(text => $"ERR unknown command '{text}'");
Assert.Equal("ERR unknown command 'PNIG'", await client.SendAndReceiveAsync("PNIG"));
Assert.Equal("PONG", await client.SendAndReceiveAsync("PING")); // still connected
Assert.Single(server.Mock.UnmatchedRequests); // still reportedserver.Mock.Configs lists the exact-request configurations, keyed by request bytes. Patterns, predicates and rules
for a state aren't included. Each Config has a CallCount:
Assert.Equal(2, server.Mock.Configs["LIST".GetBytes()].CallCount);To check how often a request arrived, server.Should().HaveReceived(...) is usually clearer.
Runnable code: PartialMatchingSamples.cs,
ResponseSamples.cs,
UnmatchedRequestSamples.cs