Patrick McCurley
Patrick McCurley

Reputation: 2054

How to unit test this code with private implementation?

I know there are questions are are similar to this, but none seem to satisfy the correct way to address the following code snippet in regards to unit testing

public class InMemoryQueue : ICloudQueue
{
    private Queue<object> _inMemoryCollection = new Queue<object>();
    private readonly ServiceBusQueueSettings _settings;

    public InMemoryQueue(ServiceBusQueueSettings settings = null)
    {
        _settings = settings;

        if (_settings == null) _settings = new ServiceBusQueueSettings();
    }

    public async Task<IEnumerable<T>> ReceieveAsync<T>(int batchCount)
    {
        List<T> result = new List<T>();
        for (int i = 0; i < batchCount; i++)
        {
            _inMemoryCollection.Dequeue();
        }
        return result;
    }

    public async Task AddToAsync<T>(IEnumerable<T> items)
    {
        items.ToList().ForEach(x=>_inMemoryCollection.Enqueue(x));
    }
}

OK, I want to unit this this implementation of ICloudQueue to ensure that items that are added to via AddToAsync are held in memory, and any call to RecieveAsync is pulled from memory.

However - the complication is that the data is held in _inMemoryCollection which is a private variable. If the _inMemoryCollection queue WASNT private, one of the tests might look like this :

    [Test]
    public async Task Adding_To_Queue_Results_In_Message_Added_To_List()
    {
        //arrange
        var listToAdd = new List<object>() { new object() };
        var inMemoryQueue = new InMemoryQueue();

        //act
        await inMemoryQueue.AddToAsync(listToAdd);

        //assert
        Assert.IsTrue(inMemoryQueue._inMemoryCollection.Count == 1);
    }

I guess I could change the _inMemoryCollection to be internal, then use the [InternalsVisibleTo] attribute to expose it to the unit tests. Although I'm reluctant as it means changing the implementation just to satisfy unit tests.

Is this my only option?

Upvotes: 2

Views: 393

Answers (3)

Konrad Kokosa
Konrad Kokosa

Reputation: 16878

A real implementation of the queue within InMemoryQueue can be provided by the dependency injection (by constructor for example), which will allow you to test it:

public class InMemoryQueue : ICloudQueue
{
    private IQueue<object> _queue;
    private readonly ServiceBusQueueSettings _settings;

    public InMemoryQueue(IQueue<object> queue, ServiceBusQueueSettings settings = null)
    {
        _queue = queue;
        _settings = settings;

        if (_settings == null) _settings = new ServiceBusQueueSettings();
    }

    public async Task<IEnumerable<T>> ReceieveAsync<T>(int batchCount)
    {
        List<T> result = new List<T>();
        for (int i = 0; i < batchCount; i++)
        {
            _queue.Dequeue();
        }
        return result;
    }

    public async Task AddToAsync<T>(IEnumerable<T> items)
    {
        items.ToList().ForEach(x => _queue.Enqueue(x));
    }
}

Unfortunatelly, System.Collections.Generic.Queue<T> do not implement any IQueue<T> interface, so you must to introduce it:

public interface IQueue<T>
{
    void Enqueue(T item);
    T Dequeue();
    int Count { get; }
}

So to test InMemoryQueue with Queue<T>, a wrapper must be written:

public class QueueWrapper<T> : IQueue<T>
{
    private Queue<T> queue; 
    public QueueWrapper()
    {
        queue = new Queue<T>();
    }
    public void Enqueue(T item)
    {
        queue.Enqueue(item);
    }

    public T Dequeue()
    {
        return queue.Dequeue();
    }

    public int Count
    {
        get
        {
            return queue.Count;
        }
    }
}

And eventually test looks:

public async void TestMethod()
{
    //arrange
    var listToAdd = new List<object>() { new object() };
    var queue = new QueueWrapper<object>();
    var inMemoryQueue = new InMemoryQueue(queue);

    //act
    await inMemoryQueue.AddToAsync(listToAdd);

    //assert
    Assert.IsTrue(queue.Count == 1);
}

Upvotes: 1

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236218

I think you can check if items which you added to queue was queued by receiving them back (actually this is expected behavior of your queue):

[Test]
public void Can_Receive_Added_Items()
{
    //arrange        
    var listToAdd = new List<object> { new object() };
    var inMemoryQueue = new InMemoryQueue();

    //act
    inMemoryQueue.AddToAsync(listToAdd).Wait();

    //assert
    var queuedItems = inMemoryQueue.ReceieveAsync(listToAdd.Count).Result;
    CollectionAssert.AreEqual(listToAdd, queuedItems);
}

BTW Currently you are not adding items to result list when dequeueing them, so your test will fail.

Upvotes: 3

wOOdy...
wOOdy...

Reputation: 669

I think the only way is to change the approach of your testing.

You will have to write your test from the process perspective.

First perform an Add, and then perform a Receive. You should have to assert the element you have received is the same that you have added.

If you want to ensure there was just one element added, perform a Receive again an ensure you get an empty list.

Upvotes: 0

Related Questions