如何单元测试依赖于HttpContext的控制器

时间:2015-09-21 17:47:50

标签: c# unit-testing moq

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

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

1 个答案:

答案 0 :(得分:0)

将控制器耦合到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
}