【问题标题】:How to Unit Test a controller that dependends on HttpContext如何对依赖于 HttpContext 的控制器进行单元测试
【发布时间】:2015-09-21 17:47:50
【问题描述】:

我想知道当我的控制器从依赖于HttpContext 的基本控制器继承时如何对它进行单元测试。下面是我继承的控制器,名为 BaseInterimController。下面是我希望进行单元测试的AccountController 方法。我们正在使用最小起订量。

public abstract class BaseInterimController : Controller
{

    #region Properties
    protected string InterimName
    {
        get { return MultiInterim.GetInterimName(InterimIdentifier); }
    }

    internal virtual string InterimIdentifier
    {
        get { return System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["InterimIdentifier"].ToString(); }
    }
}

public class AccountController : BaseInterimController
{
    [HttpPost]
    [AllowAnonymous]
    [ValidateInput(false)]
    [Route(@"{InterimIdentifier:regex([a-z]{7}\d{4})}/Account/Signin")]
    public ActionResult Signin(LoginViewModel model)
    {
        if (ModelState.IsValid)
        {
            var identity = Authentication.SignIn(model.Username,
                model.Password) as LegIdentity;

            if (identity != null && identity.IsAuthenticated)
            {
                return Redirect(model.ReturnUrl);
            }
            else
            {
                // Sign in failed
                ModelState.AddModelError("",
                    Authentication.ExternalSignInFailedMessage);
            }
        }
        return View(model);
    }
}

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    将您的控制器与HttpContext 耦合可能会使您的代码非常难以测试,因为在单元测试期间HttpContext 为空,除非您尝试模拟它;你真的不应该这样做。不要模拟你不拥有的代码。

    尝试将您想要从 HttpContext 获取的功能抽象为您可以控制的东西。

    这只是一个例子。如果需要,您可以尝试使其更加通用。我将专注于您的具体情况。

    你直接在你的控制器中调用它

    System.Web.HttpContext.Current.Request
        .RequestContext.RouteData.Values["InterimIdentifier"].ToString();
    

    当您真正追求的是获得InterimIdentifier 值的能力时。类似的东西

    public interface IInterimIdentityProvider {
           string InterimIdentifier { get; }
    }
    
    public class ConcreteInterimIdentityProvider : IInterimIdentityProvider {
        public virtual string InterimIdentifier {
            get { return System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["InterimIdentifier"].ToString(); }
        }
    }
    

    稍后可以在具体类中实现并注入到您的控制器中,前提是您使用的是依赖注入。

    你的基本控制器看起来像

    public abstract class BaseInterimController : Controller {
        protected IInterimIdentityProvider identifier;
        public BaseInterimController(IInterimIdentityProvider identifier) {
            this.identifier = identifier;
        }
    
        protected string InterimName {
            get { return MultiInterim.GetInterimName(identifier.InterimIdentifier); }
        }
    
        //This can be refactored to the code above or use what you had before
        //internal virtual string InterimIdentifier {
        //    get { return identifier.InterimIdentifier; }
        //}
    }    
    
    public class AccountController : BaseInterimController
    {
        public AccountController(IInterimIdentityProvider identifier) 
            : base(identifier){ }
    
        [HttpPost]
        [AllowAnonymous]
        [ValidateInput(false)]
        [Route(@"{InterimIdentifier:regex([a-z]{7}\d{4})}/Account/Signin")]
        public ActionResult Signin(LoginViewModel model)
        {
            if (ModelState.IsValid)
            {
                var identity = Authentication.SignIn(model.Username,
                    model.Password) as LegIdentity;
    
                if (identity != null && identity.IsAuthenticated)
                {
                    return Redirect(model.ReturnUrl);
                }
                else
                {
                    // Sign in failed
                    ModelState.AddModelError("",
                        Authentication.ExternalSignInFailedMessage);
                }
            }
            return View(model);
        }
    }
    

    这允许实现的控制器不依赖于HttpContext,这将允许更好的单元测试,因为您可以使用 Moq 轻松模拟/伪造IInterimIdentityProvider 接口,以便在测试期间返回您想要的内容。

    [TestMethod]
    public void Account_Controller_Should_Signin() {
        //Arrange
        var mock = new Mock<IInterimIdentityProvider>();
        mock.Setup(m => m.InterimIdentifier).Returns("My identifier string");
        var controller = new AccountController(mock.Object);
        var model = new LoginViewModel() {
            Username = "TestUser",
            Password = ""TestPassword
        };
        //Act
        var actionResult = controller.Signin(model);
        //Assert
        //...assert your expected results
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-26
      • 1970-01-01
      • 2014-05-29
      • 1970-01-01
      相关资源
      最近更新 更多