After the uncommented code, and then the commented version of the code, you finally get to see the tests that verify that solution protects the code from the issue it was facing. I think all 3 posts (and the comments on them) sufficiently explain the problem and the solution so i won't go through the trouble of explaining everything in this post. The tests however, might not be very clear to everyone. I'm only posting 3 tests, though there are more but then the post would just be way too long.
These tests use the following 2 fields:
private Broadcaster broadcaster;
private IClientProxyFactory clientFactory;
Which are set up before each test like this:
clientFactory = MockRepository.GenerateMock<IClientProxyFactory>();
broadcaster = new Broadcaster(clientFactory);
First of all, take a look at some of the utility methods that these tests use:
private List<IClientProxy> GetBroadcastersClients()
{
var clientsFieldInfo = typeof(Broadcaster).GetField("clients", BindingFlags.NonPublic | BindingFlags.Instance);
return (List<IClientProxy>)clientsFieldInfo.GetValue(broadcaster);
}
private Exception GetExceptionThrownBy(Action yourCode)
{
try { yourCode(); } catch (Exception e) { return e; }
return null;
}
private void RegisterClientWithBroadcaster(IClientProxy client)
{
clientFactory.Stub(f => f.CreateClientProxyForCurrentContext(null))
.IgnoreArguments().Return(client).Repeat.Once();
broadcaster.Register();
}
private IClientProxy RegisterClientWithImplementationForSend(Action implementation)
{
var client = MockRepository.GenerateMock<IClientProxy>();
client.Stub(c => c.SendNotificationAsynchronously(null))
.IgnoreArguments().WhenCalled(obj => implementation());
RegisterClientWithBroadcaster(client);
return client;
}
private IClientProxy RegisterClientWithEmptySendImplementation()
{
return RegisterClientWithImplementationForSend(() => { });
}
private void RegisterClientsWithImplementationForSend(int number, Action implementation)
{
var clients = new IClientProxy[number];
for (int i = 0; i < number; i++)
{
clients[i] = RegisterClientWithImplementationForSend(implementation);
}
}
And then the actual tests:
[Test]
public void RegisterClientWhileBroadcasting_ClientIsAddedAndBroadcastingDidntThrowException()
{
RegisterClientsWithImplementationForSend(5, () => Thread.Sleep(50));
Exception exceptionFromBroadcastThread = null;
var broadcastThread =
new Thread(() => exceptionFromBroadcastThread = GetExceptionThrownBy(() => broadcaster.Broadcast(null)));
broadcastThread.Start();
Thread.Sleep(50);
var newClient = RegisterClientWithEmptySendImplementation();
broadcastThread.Join();
Assert.IsNull(exceptionFromBroadcastThread);
Assert.That(GetBroadcastersClients().Contains(newClient));
}
[Test]
public void ClientFaultedWhileBroadcasting_FaultedClientIsRemovedFromClientsList()
{
RegisterClientsWithImplementationForSend(2, () => { });
var faultyClient = MockRepository.GenerateMock<IClientProxy>();
faultyClient.Stub(c => c.SendNotificationAsynchronously(null))
.IgnoreArguments().WhenCalled(obj => faultyClient.Raise(c => c.Faulted += null, faultyClient, EventArgs.Empty));
RegisterClientWithBroadcaster(faultyClient);
RegisterClientsWithImplementationForSend(2, () => { });
broadcaster.Broadcast(null);
Assert.IsFalse(GetBroadcastersClients().Contains(faultyClient));
}
[Test]
public void ClientFaultedInSeparateThreadWhileBroadcasting_FaultedClientIsRemovedWithoutExceptionDuringBroadcasting()
{
var faultyClient = RegisterClientWithEmptySendImplementation();
RegisterClientsWithImplementationForSend(10, () => Thread.Sleep(50));
Exception exceptionFromBroadcastThread = null;
var broadcastThread =
new Thread(() => exceptionFromBroadcastThread = GetExceptionThrownBy(() => broadcaster.Broadcast(null)));
broadcastThread.Start();
Thread.Sleep(150);
faultyClient.Raise(c => c.Faulted += null, faultyClient, EventArgs.Empty);
broadcastThread.Join();
Assert.IsNull(exceptionFromBroadcastThread);
Assert.IsFalse(GetBroadcastersClients().Contains(faultyClient));
}
Note: i'm not sure if this is actually the best way to test this code... there will probably be better solutions for testing threading issues.
Pingback: Dew Drop - February 23, 2009 | Alvin Ashcraft's Morning Dew
Pingback: Logging In « Search And Destroy