У меня есть класс с именем TcpConnector
, который вызывает событие, когда соединение с конечной точкой успешно завершено.
Это сокращенная реализация:
public class TcpConnectorEventArgs : EventArgs
{
public Exception EventException { get; set; }
[...]
}
public class TcpConnector
{
public event EventHandler<TcpConnectorEventArgs> EventDispatcher;
public void BeginConnect(IPEndPoint endpoint, int timeoutMillis)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ipcState = new IpcState()
{
IpcSocket = socket,
IpcEndpoint = endpoint,
IpcTimeoutMillis = timeoutMillis
};
try
{
ipcState.IpcSocket.BeginConnect(ipcState.IpcEndpoint, HandleConnect, ipcState);
}
catch (Exception ex)
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = ex
};
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}
private void HandleConnect(IAsyncResult asyncResult)
{
var ipcState = asyncResult.AsyncState as IpcState;
if (ipcState == null)
{
return;
}
try
{
var result = asyncResult.AsyncWaitHandle.WaitOne(ipcState.IpcTimeoutMillis, true);
if (result)
{
ipcState.IpcSocket.EndConnect(asyncResult);
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectSuccess
};
// Raise event with details
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
// Check cancellation flag if any subscriber wants the
// connection canceled
if (tcpConnectorEventArgs.EventCancel)
{
ipcState.IpcSocket.Close();
}
}
else
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = new SocketException(10060) // Connection timed out
};
// Raise event with details about error
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}
catch (Exception ex)
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = ex
};
// Raise event with details about error
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}
}
Это тест, который я использую:
[Fact]
[Trait(TraitKey.Category, TraitValue.UnitTest)]
public void Should_Raise_Event_And_Fail_To_Connect()
{
// Arrange
var receivedEvents = new List<TcpConnectorEventArgs>();
var nonListeningPort = 82;
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), nonListeningPort);
var timeout = 1 * 1000;
var client = new TcpConnector();
client.EventDispatcher += (o, e) => receivedEvents.Add(e);
// Act
client.BeginConnect(endPoint, timeout);
Thread.Sleep(10 * 1000);
// Assert
receivedEvents.Should().HaveCount(1);
receivedEvents[0].EventType.Should().Be(TcpConnectorEventTypes.EventConnectFailure);
receivedEvents[0].EventException.Message.Should().Be("No connection could be made because the target machine actively refused it");
}
Поскольку мой метод BeginConnect()
выполняется асинхронно и поэтому не выполняетНе блокируйте абонента, я придумал глупый подход использования Thread.Sleep()
.Это однако чувствует себя неправильно.
Итак, вопрос в том, как можно «правильно» проверить этот метод?Особенно для правильного поведения тайм-аута.
Мое решение
Для полноты картины вот так выглядит мой класс и тест, используя ConnectAsync()
public class TcpConnector
{
private Socket socket;
//[...]
public async Task ConnectAsync(IPEndPoint endpoint)
{
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await this.socket.ConnectAsync(endpoint);
}
}
И два примера тестов xUnit ...
[Fact]
[Trait("Category", "UnitTest")]
public async Task Should_Successfully_ConnectAsync()
{
// Arrange
var client = new TcpConnector();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
// Act
var connectTask = client.ConnectAsync(endpoint);
await connectTask;
// Assert
connectTask.IsCompletedSuccessfully.Should().BeTrue();
connectTask.Exception.Should().BeNull();
client.IsConnected().Should().BeTrue();
}
[Fact]
[Trait("Category", "UnitTest")]
public async Task Should_Throw_Exception_If_Port_Unreachable()
{
// Arrange
var client = new TcpConnector();
var nonListeningPort = 81;
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), nonListeningPort);
// Act & Assert
var connectTask = client.ConnectAsync(endpoint);
Func<Task> func = async () => { await connectTask; };
func.Should().Throw<Exception>();
}