【问题标题】:How to Mock/Stub or simply ignore HttpRequest when unit testing [duplicate]单元测试时如何模拟/存根或简单地忽略 HttpRequest [重复]
【发布时间】:2016-03-01 07:23:01
【问题描述】:
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 模块。

我如何模拟/避免访问这个(Request.ServerVariables["HTTP_X_REWRITE_URL"].ToString())

这是导致所有问题的原因。

【问题讨论】:

    标签: asp.net-mvc unit-testing dependency-injection moq


    【解决方案1】:

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

            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. Mock&lt;IDeviceDetection&gt; 允许您在设置时引发异常,以测试您的 try-catch 块的异常处理。
    3. 你可以断言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());
    }
    

    【讨论】:

    • 在我构建 DetectDevice 时,它​​会给我空引用异常。
    • 你能贴出你的测试代码吗?
    • 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);
    • var ctrCtx = new Mock(); ctrCtx.Setup(x => x.HttpContext).Returns(httpContext.Object); usedController.ControllerContext = ctrCtx.Object; usedController.Default();
    • 这是代码。请不要混淆使用/演示控制器。
    【解决方案2】:

    使DeviceDetection 成为concrete dependencyDemoController

    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 变量。

    【讨论】:

    • 我不知道控制器是如何/从哪里被精确实例化的。我知道如何为单元测试做这件事,但我如何为原始控制器定义做这件事?
    • 您已经在DemoController 中依赖于ICommonOperationsRepository。你是怎么写的?
    • 我已经放入 Unity Bootstraper 文件:container.RegisterType()
    • 我不知道 Unity 的 MVC 集成(如果有的话)是如何工作的,但是使用 Pure DI 很容易。实现IControllerFactory,它提供了您需要的所有构建块。
    • 这行不通,因为我也必须跳过/模拟检测设备方法的实现,因为它访问了一些我不想从单元测试中传递的 HttpContext 变量。
    猜你喜欢
    • 2014-03-29
    • 2019-10-27
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 2011-09-15
    • 1970-01-01
    • 2013-08-29
    • 1970-01-01
    相关资源
    最近更新 更多