Writing integration tests¶
This page is a recipe book for testing code that uses RhpV2.Client.
The library's own xunit suite (31 tests) follows these patterns; yours
can too.
Skeleton¶
using RhpV2.Client;
using RhpV2.Client.Protocol;
using RhpV2.Client.Testing;
using Xunit;
public class MyAppTests
{
[Fact]
public async Task Sends_Beacon_On_Startup()
{
await using var server = new MockRhpServer();
server.Start();
await using var client = await RhpClient.ConnectAsync(
"127.0.0.1", server.Endpoint.Port);
// ... drive your code under test ...
await MyApp.SendBeaconAsync(client);
// Assert on what the client sent.
var sent = server.ReceivedFrames.OfType<SendToMessage>().Single();
Assert.Equal("BEACON", sent.Remote);
}
}
Awaiting an asynchronous notification¶
A common pattern: the mock pushes a frame, you wait for the matching event to fire, with a timeout so a regression can't hang the suite.
var tcs = new TaskCompletionSource<RecvMessage>(
TaskCreationOptions.RunContinuationsAsynchronously);
client.Received += (_, e) => tcs.TrySetResult(e.Message);
await server.BroadcastAsync(new RecvMessage
{
Handle = handle,
Data = "ping\r",
});
var got = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
Assert.Equal("ping\r", got.Data);
Forcing a server-side error¶
server.Handler = msg => msg switch
{
SendMessage s => new SendReplyMessage
{
Handle = s.Handle,
ErrCode = RhpErrorCode.NoBuffers,
ErrText = "No buffers",
},
_ => null,
};
var ex = await Assert.ThrowsAsync<RhpServerException>(
() => client.SendOnHandleAsync(handle, "..."));
Assert.Equal(RhpErrorCode.NoBuffers, ex.ErrorCode);
Testing transport teardown¶
var server = new MockRhpServer { SuppressReplies = true };
server.Start();
await using var client = await RhpClient.ConnectAsync(
"127.0.0.1", server.Endpoint.Port);
var task = client.OpenAsync(
ProtocolFamily.Ax25, SocketMode.Stream,
port: "1", local: "G8PZT", flags: OpenFlags.Passive);
await Task.Delay(50); // let the request reach the mock
await server.DisposeAsync(); // tear it down
await Assert.ThrowsAnyAsync<RhpProtocolException>(async () => await task);
Cookbook: parallel correlation¶
The library's id-based correlation is fully parallel-safe. This is the test that proves it:
var tasks = Enumerable.Range(0, 32)
.Select(_ => client.OpenAsync(
ProtocolFamily.Ax25, SocketMode.Stream,
port: "1", local: "G8PZT", flags: OpenFlags.Passive))
.ToArray();
var handles = await Task.WhenAll(tasks);
Assert.Equal(handles.Length, handles.Distinct().Count());
Tips¶
- Always wrap
MockRhpServerandRhpClientinawait using. The reader loop is aTask— leaking it across tests will eventually exhaust ports. - Subscribe to events before opening sockets. Mock replies arrive fast enough to race the subscription.
- Wrap awaits with
WaitAsync(timeout)so an event that never fires fails the test instead of hanging it.