1
public class DemoController : Controller
{
    private readonly ICommonOperationsRepository _commonRepo;
    public DemoController (ICommonOperationsRepository commonRepo)
    {
        _commonRepo = commonRepo;
    }

    public ActionResult Default()
    {
        var model = new DemoModel();
        try
        {
            **DeviceDetection dd = new DeviceDetection(Request.ServerVariables["HTTP_X_REWRITE_URL"].ToString());
            dd.DetectDevice();**

            model.ListTopListing.AddRange(_commonRepo.GetListings());
        }
        catch (Exception ex)
        {
            ExceptionHandler objErr = new ExceptionHandler(ex, "DemoController .Default()\n Exception : " + ex.Message);
            objErr.LogException();
        }
        return View(model);
    }
}

Problem:The DeviceDetection has concrete dependency here so I can't unit test my controller. I don't want to Mock the Http request since I just want to test the controller and not DeviceDetection module.

How can i mock/avoid accessing this (Request.ServerVariables["HTTP_X_REWRITE_URL"].ToString())

which is causing all the problems.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Sahil Sharma
  • 3,847
  • 6
  • 48
  • 98

2 Answers2

1

Make DeviceDetection a concrete dependency of DemoController:

public class DemoController : Controller
{
    private readonly ICommonOperationsRepository _commonRepo;
    private readonly DeviceDetection dd;

    public DemoController (
        ICommonOperationsRepository commonRepo,
        DeviceDetection dd)
    {
        _commonRepo = commonRepo;
        this.dd = dd;
    }

    public ActionResult Default()
    {
        var model = new DemoModel();
        try
        {
            this.dd.DetectDevice();
            model.ListTopListing.AddRange(_commonRepo.GetListings());
        }
        catch (Exception ex)
        {
            ExceptionHandler objErr = new ExceptionHandler(ex, "DemoController .Default()\n Exception : " + ex.Message);
            objErr.LogException();
        }
        return View(model);
    }
}

This should enable you to create an instance of DemoController without relying on the Request property:

var sut = new DemoController(someStupRepository, new DeviceDetection("foo"));

You can do this in e.g. a unit test.

When you compose DemoController in your application, you pass in request.ServerVariables["HTTP_X_REWRITE_URL"].ToString() to DeviceDetection. You can get the request variable from the requestContext argument of CreateController.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • I dont know how/from-where controller is called exactly instantiated. I get how to do it for unit test but how do i do it for the original controller definition? – Sahil Sharma Nov 27 '15 at 15:31
  • You already have a dependency on `ICommonOperationsRepository` in `DemoController`. How do you compose that? – Mark Seemann Nov 27 '15 at 16:20
  • I have placed in Unity Bootstraper file: container.RegisterType() – Sahil Sharma Nov 27 '15 at 20:09
  • I don't know how Unity's MVC integration (if any) works, but with [Pure DI](http://blog.ploeh.dk/2014/06/10/pure-di) it's easy enough. Implement `IControllerFactory`, which provides all the building blocks you need. – Mark Seemann Nov 27 '15 at 20:33
  • This wont work since I have to skip/mock the implementation of detectdevice method too as its accessing some HttpContext variables which i dont want to pass from unit tests. – Sahil Sharma Dec 01 '15 at 06:31
1

To answer your question, you need the following in your test:

        var requestBase = new Mock<HttpRequestBase>();
        requestBase.Setup(r => r.ServerVariables)
               .Returns(new NameValueCollection { {"HTTP_X_REWRITE_URL", "your url"} });

        var httpContext = new Mock<HttpContextBase>();
        httpContext.Setup(x => x.Request).Returns(requestBase.Object);

        var ctrCtx = new Mock<ControllerContext>();
        ctrCtx.Setup(x => x.HttpContext).Returns(httpContext.Object);

        demoController.ControllerContext = ctrCtx.Object;  

However as @Mark suggested you don't need to create concrete instance of DeviceDetection inside your action, you need to inject it. But instead of injecting a concrete instance, it is better to wrap it into interface IDeviceDetector and inject this abstraction.

I will give a number of advantages:

  1. Your action does not depend on the implementation of DeviceDetection
  2. Mock<IDeviceDetection> allows you to raise exception on setup, to test exception handling of your try-catch block.
  3. You can assert that DetectDevice() method is called

Another suggestion - never use try{} catch(Exception ex){}, you should catch only those exceptions which you can handle. As you don't know which type of exception can be thrown and how to handle it effectively, e.g. it can be OutOfMemoryException. This article can give you basic ideas of different ways to handle exception in MVC.

UPDATE: As I see you are using Unity as IoC container. Unity has a possibility to inject constructor parameters. So again you need to extract an interface from DeviceDetector let's say IDeviceDetector. Register it

container.RegisterType<IDeviceDetector, DeviceDetector>(new InjectionConstructor(
HttpContext.Current.Request.ServerVariables["HTTP_X_REWRITE_URL"].ToString()));

Register DeviceDetector with TransientLifetimeManager.

Then your controller should look like

public class DemoController : Controller
{
    private readonly ICommonOperationsRepository _commonRepo;
    private readonly IDeviceDetection _deviceDetection;

    public DemoController (
        ICommonOperationsRepository commonRepo,
        IDeviceDetection deviceDetection)
    {
        _commonRepo = commonRepo;
        _deviceDetection = deviceDetection;
    }

    public ActionResult Default()
    {
        var model = new DemoModel();

        _deviceDetection.DetectDevice();
        model.ListTopListing.AddRange(_commonRepo.GetListings());

        return View(model);
    }
}

Note, in this case you need to write unit tests for your Unity container to verify that your injections are resolved correctly. Your unit test may look like:

[TestMethod]
public void Test()
{
    var repository = new Mock<ICommonOperationsRepository>();
    var deviceDetection = new Mock<IDeviceDetection>();

    var controller = new DemoController(repository.Object, deviceDetection.Object);
    controller.Default();

    deviceDetection.Verify(x => x.DetectDevice(), Times.Once());
}
Andrei Mihalciuc
  • 2,148
  • 16
  • 14
  • It gives me null reference exception as soon as i am constructing DetectDevice. – Sahil Sharma Nov 27 '15 at 14:41
  • Can you post the code of your test? – Andrei Mihalciuc Nov 27 '15 at 15:43
  • var usedController = new UsedController(new CommonOperationsRepository()); var requestBase = new Mock(); requestBase.Setup(r => r.ServerVariables) .Returns(new NameValueCollection { { "HTTP_X_REWRITE_URL", "/used/" } }); var httpContext = new Mock(); httpContext.Setup(x => x.Request).Returns(requestBase.Object); – Sahil Sharma Nov 27 '15 at 15:47
  • var ctrCtx = new Mock(); ctrCtx.Setup(x => x.HttpContext).Returns(httpContext.Object); usedController.ControllerContext = ctrCtx.Object; usedController.Default(); – Sahil Sharma Nov 27 '15 at 15:48
  • This is the code. Please don't confuse between used/demo controller. – Sahil Sharma Nov 27 '15 at 15:48
  • Are you sure that Request.ServerVariables["HTTP_X_REWRITE_URL"] is null? I've tried your code and Request.ServerVariables is populated. May be your DetectDevice constructor throws exeption. – Andrei Mihalciuc Nov 27 '15 at 16:07
  • Request.ServerVariables["HTTP_X_REWRITE_URL"] is not null. I have explicitly set it to "/used/" as shown above in the code. And Device Detection construction never gets hit, it throw exception right at this line only. (Object reference not set to an instance of object- Inner exception) – Sahil Sharma Nov 30 '15 at 05:22
  • Sorry, I think its from Device Detection. The exception Message says "The type initializer for 'Carwale.UI.Common.DeviceDetection' threw an exception." But we are not doing anything as such in DeviceDetection exception assigning the string. – Sahil Sharma Nov 30 '15 at 05:33
  • Is there any way by which I can simply avoid calling DetectDevice() function since anyways i do not need to test that? (Mocking such that it simply skips its execution in controller when we are running test) – Sahil Sharma Nov 30 '15 at 09:46
  • You cannot skip execution of DeviceDetection, as it is part of your code. You need to inject DeviceDetection into your constructor as you did with ICommonOperationsRepository. See my update – Andrei Mihalciuc Nov 30 '15 at 11:07
  • Sorry for using word skip, I meant mocking so that actual DeviceDetection is not called when we are unit testing and i mock implementation is invoked. – Sahil Sharma Dec 01 '15 at 07:08
  • I've updated the answer with an example of unit test – Andrei Mihalciuc Dec 01 '15 at 09:11