dimanche 8 février 2015

Asynchronous initialization and its unit testing

Background I need some class to perform background initialization, which should start in constructor. Currently I'm using a Task which is started by constructor, and then all operations, depending on that initialization wait for that Task completion.


Please take a look at the following simplified example:



interface IEntry {}

interface IRepository
{
IQueryable<IEntry> Query { get; }
void Add(IEntry entry);
}

class PrefetchedRepository : IRepository
{
private readonly Task _prefetchingTask;
private readonly ICollection<IEntry> _entries = new List<IEntry>();
private readonly IRepository _underlyingRepository;

public PrefetchedRepository(IRepository underlyingRepository)
{
_underlyingRepository = underlyingRepository;
// Background initialization starts here
_prefetchingTask = Task.Factory.StartNew(Prefetch);
}

public IQueryable<IEntry> Query
{
get
{
EnsurePrefetchCompleted();
return _entries.AsQueryable();
}
}

public void Add(IEntry entry)
{
EnsurePrefetchCompleted();
_entries.Add(entry);
_underlyingRepository.Add(entry);
}

private void EnsurePrefetchCompleted()
{
_prefetchingTask.Wait();
}

private void Prefetch()
{
foreach (var entry in _underlyingRepository.Query)
{
_entries.Add(entry);
}
}
}


This works. The problem starts when I want to test initialization in Unit Test. I'm creating the instance and providing the mock of underlying repository. I want to ensure that all entries were fetched from the mock as expected.



[TestFixture]
public class PrefetchingRepositoryTests
{
[Test]
public void WhenInitialized_PrefetchingIsDone()
{
// Arrange
var underlyingRepositoryMock = A.Fake<IRepository>();

// Act
var target = new PrefetchedRepository(_underlyingRepository);

// Assert
underlyingRepositoryMock.CallsTo(r => r.Query).MustHaveHappened(Repeated.Exactly(1));
}
}


As you can imagine, most of the time fails, because actually initialization didn't started yet at the assertion point.


Questions


Question 1 - Initialization: Is there more elegant way of asynchronous initialization rather than starting task in constructor and waiting for it in all dependent operations?


Question 2 - Testing: I thought of 2 possible ways to solve race between the test and testee:




  1. Using event handle to the test:



    [Test]
    public void WhenInitialized_PrefetchingIsDone()
    {
    // Arrange ...
    var invokedEvent = new ManualResetEvent(false);
    underlyingRepositoryMock.CallsTo(r => r.Query).Invokes(_ => invokedEvent.Set());

    // Act ...

    // Assert
    Assert.True(invokedEvent.WaitOne(1000));
    }



  2. Exposing EnsurePrefetchCompleted method as internal and using it in the Unit Test (assuming usage of [assembly: InternalsVisibleTo("...")])




The problem with both solutions is that in case of failure time duration long (actually in the second case - it is limited by test timeout).


Is there any simpler way to do this kind of testing?


Aucun commentaire:

Enregistrer un commentaire