Unit Test heartbeat functionality

Viewed 854

I have a client-server system based on TCP coded in C#. I was requested to add a bidirectional heartbeat to discover if any of the elements in the network freeze and are not responding.

I solved the problem by sending a heartbeat from the server to all connected clients and waiting for the response. There's a timeout if the response doesn't come on time and there is a timeout in the clients if the server didn't send any heartbeat in 2 seconds (assuming the server is frozen).

Everything happens internally and the server nor the client expose any of the events (receiving a heartbeat, replying a heartbeat happen silently, as it was requested)

The thing is .. How does one create a unit test or integration test for such a functionality?

Note: I'm coding in visual studio, C#, .Net 4.6.1, testing using NUnit3

Brief pseudocode example:

//we have one connector for each connected client
class connector{

    //if the echoReceived timer is not reseted on time it will complain
    Timer echoReceived = new Timer(200ms);
    //Another timer for sending beats
    Timer heartbeatSender = new Timer (1000ms);

    OnClientConnected(Client)
    {        
        echoReceived.elapsed += () => { ShowError("Client did not reply") };

        heartbeatSender.elapsed += () => {
            Send(client, new Message(Heartbeat));
            echoReceived.Enabled = true;
        });
        heartbeatSemder.isEnabled = true;
    }

    OnNewMessageFromClient(Client, message)
    {
        if(message is echoedHeartBeat)
        {
            echoReceived.Enabled= false;
            echoReceived.Reset();
        }        
    }

}

On the client side

class client 
{
     Timer ServerDeadTimeOut = new Timer (1000ms);

     OnStart()
     {
         serverDeadTimeOut.Elapsed += () => { ShowError("Server is dead"); };
         serverDeadTimeOut.isEnabled = true;
     }

     OnNewMessageFromServer(message)
     {
         if(message is HeartBeatMessage)
         {
             serverDeadTimeOut.Reset();
             Send(new HeartBeatEchoMessage);
         }
     }
}
2 Answers

I'd go for Unit Tests, integration would require setting up some kind of connection between these two, depending on implementation it can be easier or harder. Unit Tests would simply rely on the abstraction. Just inject interfaces to the Connector and for unit testing replace them with Mocks to simulate scenarios that you designed that class for. Of course then you have to test actual implementations of Timer, Logger and ClientListener, but that's what unit tests are for.

public class ConnectorTest
{
    private readonly Connector _connector;
    private readonly TimerMock _receiverMock;
    private readonly TimerMock _senderMock;
    private readonly LoggerMock _loggerMock;
    private readonly ClientListenerMock _listenerMock;

    public void Setup()
    {
        _listenerMock = new ClientListenerMock();
        _receiverMock = new TimerMock();
        _senderMock  = new TimerMock();
        _loggerMock = new LoggerMock();
        _connector = new Connector(_listenerMock, _receiverMock, _senderMock, _loggerMock)
    }

    public void HeartbeatSender_ShouldBeEnabled_OnceClientIsConnected()
    {
        _listenerMock.SimulateClientConnection();

        Assert.IsTrue(_senderMock.IsEnabled);
    }

    public void Error_ShouldBeLogged_WhenConnectedClientDidNotEchoed()
    {
        _listenerMock.SimulateClientConnection();

        _receiverMock.SimulateEchoTimeout();

        Assert.AreEqual("Client did not reply", _loggerMock.RecentErrorMessage);
    }
}

I think unit testing this class would be fairly simple if we just make a couple of small changes. First of all you'd surely like to get rid of the following:

Timer echoReceived = new Timer(200ms);

Waiting 200ms in a each unit test doesn't sound like a good idea. What you could do instead, is to extract this 200 ms into a class field initialized from the constructor so that you can pass it lower values, such as 1ms. But actually, you could make it even better by defining an interface like ITimer that could look something like:

interface ITimer 
{
    int Interval { get; }
    event EventHandler OnElapsed;  
    void Reset();
}

This way, in the unit test you could provide a stub e.g. implemented like this

class StubTimer 
{
   [...]
   void TickNow() 
   {
      OnElapsed.Invoke( ... ) // fire the event, to avoid Thread.Sleep (waiting n miliseconds)
   }
   [...]
}

You probabbly should also take ShowError function as constructor parameter, assigning it to property of type like Action<ErrorArgs> .Then, you can just unit test it like so:

public void WhenClientConnected_ButThereIsNoResponse_ItShouldCallShowError() 
{
   var stubHearbeatTimer = new StubTimer();
   var stubTimeoutTimer = new StubTimer();

   // i'm pretty sure it's possibile mock Actions with moq as well
   var wasSendErrorCalled = false;
   var stubErrorAction = (args) => {
      wasSendErrorCalled = true;
   };

   var sut = new connector(stubHearbeatTimer, stubTimeoutTimer, stubErrorAction );
   sut.OnClientConnected( ..dummyClient..);
   stubTimeoutTimer.TickNow();

   Assert.IsTrue(wasSendErrorCalled);
}

Please note it's just a pseudecode though. Hope this answers your question!

Related