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:
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));
}Exposing
EnsurePrefetchCompletedmethod 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