单元测试时如何 Mock/Stub 或干脆忽略 HttpRequest

How to Mock/Stub or simply ignore HttpRequest when unit testing

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);
    }
}

问题:DeviceDetection 在这里有具体的依赖关系,所以我无法对我的控制器进行单元测试。我不想模拟 Http 请求,因为我只想测试控制器而不是 DeviceDetection 模块。

我如何 mock/avoid 访问这个 (Request.ServerVariables["HTTP_X_REWRITE_URL"].ToString())

这是所有问题的根源。

使 DeviceDetection 成为 DemoControllerconcrete dependency:

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);
    }
}

这应该使您能够创建 DemoController 的实例,而无需依赖 Request 属性:

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

您可以在例如单元测试。

当您在应用程序中编写 DemoController 时,您将 request.ServerVariables["HTTP_X_REWRITE_URL"].ToString() 传递给 DeviceDetection。您可以从 CreateControllerrequestContext 参数中获取 request 变量。

要回答您的问题,您需要在测试中满足以下条件:

        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;  

然而,正如@Mark 建议的那样,您不需要在操作中创建 DeviceDetection 的具体实例,您需要注入它。但是与其注入一个具体的实例,不如将其包装到接口 IDeviceDetector 中并注入这个抽象。

我会给出一些优点:

  1. 您的操作不依赖于 DeviceDetection
  2. 的实施
  3. Mock<IDeviceDetection> 允许您在设置时引发异常,以测试 try-catch 块的异常处理。
  4. 您可以断言 DetectDevice() 方法被调用

另一个建议 - 永远不要使用 try{} catch(Exception ex){},你应该只捕获那些你可以处理的异常。因为您不知道可以抛出哪种类型的异常以及如何有效地处理它,例如它可以是 OutOfMemoryExceptionThis article 可以为您提供在 MVC 中处理异常的不同方式的基本思路。

更新: 如我所见,您正在使用 Unity 作为 IoC 容器。 Unity有可能inject constructor parameters。因此,您需要再次从 DeviceDetector 中提取接口,比方说 IDeviceDetector。注册它

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

TransientLifetimeManager 注册 DeviceDetector

那么你的控制器应该看起来像

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);
    }
}

请注意,在这种情况下,您需要为您的 Unity 容器编写单元测试以验证您的注入是否已正确解析。您的单元测试可能如下所示:

[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());
}