4

I have some troubles with mocking of a local variable in a method of my class.

I try to mock my Worker class but it calls a method that returns null value and my context variable becomes null. Thereby I get an exception when try to get the Name property.

How to force CreateWorkerContext() to return mock values? May be there is a way to mock a local variable (context)?

Thanks!

My code will tell more about the problem:

namespace Moq
{
    class Program
    {
        static void Main(string[] args)
        {
            var workerMock = new Mock<Worker>();
            workerMock.Object.GetContextName();
        }
    }

    public class Worker
    {
        public string GetContextName()
        {
            // something happens and context does not create (imitated situation)
            WorkerContext context = CreateWorkerContext();

            // exception because _context is null
            return context.Name;
        }

        private WorkerContext CreateWorkerContext()
        {
            // imitate that context is not created
            return null;
        }
    }

    public class WorkerContext
    {
        public string Name { get; set; }
    }
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
yurart
  • 593
  • 1
  • 6
  • 23
  • 1
    You need to extract or change the `WorkerContext CreateWorkerContext()` entirely. Maybe inject an `IWorkerContextFactory` into the `Worker`'s constructor. – CodeCaster Oct 09 '15 at 09:03
  • 1 for the factory. Or make the `IWorkerContex` injectable. – rbm Oct 09 '15 at 09:07
  • So there is no way to do mocking without changing the initial code. Sad – yurart Oct 09 '15 at 09:13
  • 1
    Unfortunately not, you can't just mock anything, it has to be an interface or a class with virtual methods on it. – Callum Bradbury Oct 09 '15 at 09:15
  • 3
    well, it's not sad, it's just that your current architecture is not easily testable, because it doesn't respect very well the SOLID principles (https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) – Fabio Salvalai Oct 09 '15 at 09:15
  • 1
    You need to inject the object to get the correct results. however you are mocking the partially and expecting a part to return a correct result. is it really how you want to write? In case you want to use this mocked class to test something else, can't you lock the GetContextName() to return Dummy string and then use it? – K D Oct 09 '15 at 09:16

2 Answers2

5

A few things are arguable here.

First, - but this is just my opinion - you should avoid having partial mocking (partial mocking = mock an abstract class that doesn't implement an interface, and therefore, keeps the original implementation of the methods which weren't mocked.). The best would be to have an IWorker interface which Worker would imlepement.

Second - I would create strict mocks. Loose mocks seem like a nice shortcut, but often will let your methods and properties return the default value when you don't intend to (null in your case)

Third - I would Inject WorkerContext. If you can't inject it because you need to parametrize it with come .ctor arguments, then inject a WorkerContextFactory which will allow you to mock the creation and parametrization of your WorkerContext

Fabio Salvalai
  • 2,479
  • 17
  • 30
4

Dynamic mock libraries like Moq are, in general, not magic. They can only replace the behaviour that you yourself could replace with code.

In this particular example, Moq can't replace CreateWorkerContext because it's a private method.

One option is to make it virtual:

public class Worker
{
    public string GetContextName()
    {
        WorkerContext context = CreateWorkerContext();
        return context.Name;
    }

    public virtual WorkerContext CreateWorkerContext()
    {
        return new WorkerContext();
    }
}

Another option would be to replace the CreateWorkerContext method with a Strategy:

public class Worker
{
    private readonly IWorkerContextFactory factory;

    public Worker(IWorkerContextFactory factory)
    {
        if (factory == null)
            throw new ArgumentNullException(nameof(factory));

        this.factory = factory;
    }

    public string GetContextName()
    {
        WorkerContext context = this.factory.Create();
        return context.Name;
    }
}

Both of these options would enable Moq to simulate the desired behaviour.

Community
  • 1
  • 1
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736