Search code examples
c#unit-testingnunitjustmock

Unit testing methods on another thread


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?


Solution

  • 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.