public class Composer
{
private Task _ComposerTask;
private ConcurrentQueue<IValue> _Values;
public bool IsConnected { get; }
// Other dependencies
private IClient _Client;
private IWriter _Writer
public Task async ConnectAsync()
{
this.IsConnected = await _Client.ConnectAsync();
_ComposerTask = Task.Run(() => this.Start());
}
private void Start()
{
while(this.IsConnected)
{
IValue value;
if(_Values.TryDequeue(out value) == false)
continue;
_Writer.Write(value);
}
}
public void Send(IValue value)
{
_Values.Enqueue(value);
}
}
When connected successfully Composer
class execute Start
method asynchronously(on another thread).
Start
method check queue of values and send it forward if value exists.
My problem in testing a Send
method.
[Test]
public void Send_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var fakeValue = Mock.Create<IValue>();
var fakeWriter = Mock.Create<IWriter>();
var fakeClient = Mock.Create<IClient>();
Mock.Arrange(() => fakeClient.ConnectAsync().Returns(CompletedTask);
var composer = new Composer(fakeClient, fakeWriter);
// for (int i = 0; i < 10; i++)
// {
// composer.Send(Mock.Create<IValue>());
// }
composer.ConnectAsync().Wait();
// Act
composer.Send(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
With commented for loop
test passed. But if for loop
executed and inner queue will be filled with even 10 values before expected value added, then test fails with message: expected at least once, but occurs 0 times.
As I understand assertion occurs before value was queued by another thread, but how this kind of behavior can be tested?
My solution which I came up with is redesign Composer
class or to be more specific change Send
method to asynchronous:
public Task SendAsync(IValue value)
{
}
Idea behind is return Task
which will completes when given value composed forward on "background" thread.
Unit test only need to await
until task completed and assert proper execution.
[Test]
public async Task SendAsync_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var composer = TestFactory.GenerateComposer();
// var tasks = new List<Task>();
// for (int i = 0; i < 10; i++)
// {
// tasks.Add(composer.SendAsync(Mock.Create<IValue>()));
// }
await composer.ConnectAsync();
// Act
await composer.SendAsync(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
My original unit test wasn't successful even without extra values added in for loop
. Test failed occasionally if it was ran multiply times.
I think the reason is "unpredictable" work of thread pool.
I am still not sure how to deal with the Task
which need to run during full lifetime of the instance where it was started, but this will be another question.