Since 64-Bit Mazers, I've started to write some tests to ensure the networking code that I have doesn't break (one of the nice things about unit and integration testing).

The conundrum that I was facing when considering how to create integration tests for this, is that the basic network code that I have works in such a way that you need to start up multiple separate instances of the game - some acting as clients and at most one acting as a server. Clients send to servers, servers distribute messages to connected clients etc...

For testing this in a unit test, I want everything on the same computer. This however means you'll need to use multiple threads, with at least one server thread continually listening for incoming connections, while the other thread is the client making those connections. Previously if I were to consider doing this I'd use pthreads in Linux or CreateThread in Windows like I did here, but C++ now supports std::threads natively!

Long story short I can create a 'server' by having it continually listen in its own thread:

void StartNetworkServer()
{
	Networking::Get()->InitializeWinSock();
	ServerListening = true;
	ListeningThread = thread([&]()
	{
		// Make a new server connection
		Server = std::make_shared<GameServer>(ServerAddress, ListeningPort, false /* using UDP*/);
		Server->Initialize();

		// Wait for connections
		while (ServerListening)
		{
			Server->Listen();
		}

		// Were done
		Server->Disconnect();
	});

	// Wait for the server to be ready
	while(Server == nullptr)
	{
		Sleep(250);
	}
}

In C++, you can create a thread as simply as using std::thread and passing a function. Very nice and easy. And because this is part of the standard, this code will work in any platform that supports C++. 

Anyway, with this in place, I can write test cases where each test acts as the client and sends network messages to it:

TEST_F(NetworkingTests, TestPing)
{
	StartNetworkServer();

	// Setup client
	Client = make_shared<GameClient>(ClientNickName, false /* using UDP*/);
	Client->Initialize();
	Client->Connect(Server);
	
	// When pinging the game server...
	Client->PingGameServer();

	// Wait for the server to respond
	Sleep(1000);

	// Read pong response
	Client->Listen();

	// Collect events raised

	const auto [clientEmittedEvents, serverEmittedEvents] = PartitionEvents();
	
	EXPECT_EQ(clientEmittedEvents.size(), 1) << "Expected 1 response that the client received the pong traffic";
	
	EnsureNetworkJoinedEventEmittedByServer(serverEmittedEvents, ClientNickName);
	
	EXPECT_EQ(serverEmittedEvents.size(), 3) << "Should be 3 joined traffic events - emitted from server";

	EnsurePlayerJoinedTrafficReceivedByServer(serverEmittedEvents, ClientNickName, ServerOrigin);	
	EnsurePlayerPingTrafficReceivedByServer(serverEmittedEvents, ClientNickName, ServerOrigin);	
	EnsurePongTrafficReceivedByClient(clientEmittedEvents, "Game Server", ClientNickName);
}

The above code uses a GameClient and GameServer class to represent the respective connections, and they both save their activity onto a queue which I can inspect to ensure that they operate as designed. For example in the above test, I (the test/client) send a connect/player-joined as well as a 'ping' message over the network to the server (which listening in that separate thread), and then I check that the server writes into the queue that it received the traffic and responded correctly. I can also see that the server sent a 'pong' response and that the client received it.

Not bad, not bad.