Pregunta

I created a class called MpvController to handle communication protocols over a .NET NamedPipeClientStream.

Now, I need to test this controller. I have a second class named MpvControllerFactory that creates the controllers.

I moved the code to establish the connection from MpvController to MpvControllerFactory, so that MpvController receives only a Stream that can be replaced with a MemoryStream.

But now, the problem is that NamedPipeClientStream doesn't behave like a standard stream. NamedPipeClientStream has neither Length nor Position. It has 2 separate buffers for Input and Output.

A MemoryStream, on the other hand, when you write to it, it moves the position to the end so there's nothing left to read. It's a continuous shared buffer for both input and output.

So then, how can I mock the behaviors of NamedPipeClientStream?

It will be important to mock to be able to test asynchronous and multi-threaded behaviors.

¿Fue útil?

Solución 2

Here's the answer. PipeStream behaves differently than normal streams, so the Controller must take in a PipeStream or NamedPipeClientStream to avoid confusion.

Initializing the connection in the factory is the right way to go.

Then all I need to do for testing is to create a NamedPipeServerStream to interact with the NamedPipeClientStream.

It's important to read the stream that's being written to, and it's important to enable Asynchronous option.

Here's the solution I came up with

private const string PipeName = "testpipe";
[NotNull]
private NamedPipeServerStream? _server;
[NotNull]
private NamedPipeClientStream? _client;
private byte[] _readBuffer = new byte[1];
[NotNull]
private IMpvControllerBase? _model;

private void InitConnection()
{
    _server = new NamedPipeServerStream(PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances,
                                            PipeTransmissionMode.Byte,
                                            PipeOptions.Asynchronous);
    _client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
    _client.Connect();
    _server.WaitForConnection();
    BeginReadLoop();
    _model = new MpvControllerBase(_client);
}

private void BeginReadLoop()
{
    _server!.BeginRead(_readBuffer, 0, 1, DataReceived, null);
}

private void DataReceived(IAsyncResult ar)
{
    if (_server.EndRead(ar) > 0)
    {
        BeginReadLoop();
    }
}

Note: This is not the proper way of listening to the pipe.

You'll eventually need to dispose your class and stop listening. There is no CancellationToken allowing you to stop the listen loop. I ended up writing a PipeStreamListener class with a StartAsync method that doesn't return until the loop stops, and doing the loop with ReadAsync and a CancellationToken. Making sure PipeStreamListener is disposable. However, this is beyond the scope of this question.

Otros consejos

The first thing I would question here is the necessity to mock out NamedPipeClientStream at all. If there are two components A and B where A depends on B, the reasons why B is mocked out in an automated test for A are usually:

  1. B is complex, and in case of an error, you want to make sure a failing test for A really shows in error A and not in B

  2. Using B in a test makes the tests for A too slow.

Here, B is NamedPipeClientStream which is a .Net framework component, which can be assumed to be well-tested enough that 1 is most probably not relevant. Moreover, AFAIK NamedPipeClientStream is an in-memory mechanism for interprocess communication, so it may not be as fast as an in-process MemoryStream, but it could be actually fast enough for most real-world tests.

If NamedPipeClientStream turns out to be indeed not fast enough for your tests, you can use the standard approach for mocking: create an interface IMyPipeClientStream, derive an class MyPipeClientStream from it which is implemented as an adapter to NamedPipeClientStream, and create another derivation MockPipeClientStream which provides a in-memory mock for the same interface.

Licenciado bajo: CC-BY-SA con atribución
scroll top