-1

I am trying to figure out whether there is some good practice how to unit test business logic in producer/consumer scenario?

Design that we use in our app is that several public methods accept requests from external system, put them in one "task queue" and then there is another thread which is responsible for processing tasks from queue. The problem is that the public method doesn't do anything complex, just enqueue new task to the queue and set manual reset event (so that the other thread can start to process new item) and all the complex code that should be tested is in private methods.

I know that I can change these private method to internal but I don't like this because then every developer could just call directly these methods instead the public ones and thus bypass the task queue completely.

So is there any way how test these private methods? Maybe small refactoring or redesign? Thanks

Skeleton of the design we use:

OrderService:

1) Public method

public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
{
    try
    {
        taskManager.ProcessWorkItem(() => OrderReceivedImpl(orderDto, callback));
    }
    catch (Exception ex)
    {
        Logger.Error(ex);

        callback(ex);
    }
}

2) Private method that I would like to test:

private void OrderReceivedImpl(OrderDto orderDto, Action<Exception> callback)
{
    try
    {
        // some business logic

        callback(null);
    }
    catch (Exception ex)
    {
        Logger.Error(ex);

        callback(ex);
    }
}

TaskManager class:

1) Method for enqueuing tasks

public void ProcessWorkItem(Action action)
{
    taskQueue.Enqueue(action);      

    newWorkItemReceived.Set();
}

2) Separate thread's method for processing tasks:

private void ProcessWorkItemQueue()
{
    while (true)
    {
        var waitResult = WaitHandle.WaitAny(new WaitHandle[] { newWorkItemReceived, stopEventReceived });

        if (waitResult == 0)
        {
            while (true)
            {
                if (taskQueue.Count == 0) break;

                Action action;

                taskQueue.TryDequeue(out action);                   

                try
                {
                    action.Invoke();
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                }

                if (stopEventReceived.WaitOne(1)) return;
            }
        }
        else
        {
            return;
        }
    }
}
Karel Bém
  • 97
  • 8
  • I can not see any problems to declare those methods *internal*. Put the whole OrderService implementation in a separate library and reference it from the main application. The main application can only use the *public* parts. **BTW** How do you keep your developers away from changing this methods from *private* to *public*? – Sir Rufo Jul 18 '17 at 07:26
  • 1
    Why are you writing all of this plumbing? Isn't this kind of thing a solved problem? What are you trying to achieve? – Enigmativity Jul 18 '17 at 07:38

4 Answers4

1

If you do not want to change the current implementation, use a mocking framework, such as Moq, and create a mock of taskManager. Your mock object can be set up so that calls to ProcessWorkItem just invoke the action immediately and allow the private method to remain private.

ZippyZippedUp
  • 458
  • 5
  • 14
1

Maybe inject your business logic into OrderService ala Strategy Pattern. Something like:

public interface IOrderReceiverStrategy
{
    void OrderReceived(OrderDto orderDto, Action<Exception> callback);
}

public class OrderReceiverStrategy : IOrderReceiverStrategy
{
    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            // some business logic

            callback(null);
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}

public class OrderService
{
    public OrderService(IOrderReceiverStrategy strategy) { }


    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            taskManager.ProcessWorkItem(() => _strategy.OrderReceived(orderDto, callback));
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}

Then you can just verify your business logic by unit testing OrderReceiverStrategy.

Ilian
  • 5,113
  • 1
  • 32
  • 41
0

You should test against state which that private method will change based on given arguments.

If that private method using external resources (database, file system, other API:s) you should abstract them and pass to the class as dependencies, where in the unit test you can mock them and assert logic against mocked dependencies.

Another approach is following "Single Responsibility principle". With this principle class should have only one reason to change.
At this moment I see next reasons

  1. You need change your class when you will change business logic for processing received order.
  2. You need change class when you change a way how you handle different orders (in Queue or in Collection or in some other way).

So you can move order processing outside of the class and pass it to the queue class as dependency.

Fabio
  • 31,528
  • 4
  • 33
  • 72
0

I will change them to internal and move to independent library. After that I will do a TestClass that can see internals and implement there some public "methodsExtensionsTesting" that proxy calls to these methods and call them in your unitTests so you can check them directly.

acostela
  • 2,597
  • 3
  • 33
  • 50