在单元测试 FormsAuthentication.SignOut() 中抛出错误

Jid*_*jan 4 c# unit-testing forms-authentication mocking httprequest

在运行我的单元测试方法时,我收到错误 FormsAuthentication.SignOut()。我已经像这样嘲笑了 httpcontext

var httpRequest = new HttpRequest("", "http://localhost/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer(
    "id",
    new SessionStateItemCollection(),
    new HttpStaticObjectsCollection(),
    10,
    true,
    HttpCookieMode.AutoDetect,
    SessionStateMode.InProc,
    false);
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
var controller = new AccountController();
var requestContext = new RequestContext(new HttpContextWrapper(httpContext), new RouteData());
controller.ControllerContext = new ControllerContext(requestContext, controller);
var actual = controller.Login(new CutomerModel() { Login = "admin", Password = "Password1" });
return httpContext;
Run Code Online (Sandbox Code Playgroud)

在登录方法中

public ActionResult Login(CutomerModel obj)
{
    FormsAuthentication.SignOut();
}
Run Code Online (Sandbox Code Playgroud)

FormsAuthentication.SignOut(); 投掷

'你调用的对象是空的。'

Nko*_*osi 5

静态方法FormsAuthentication.SignOut依赖于另一个静态成员HttpContext.Current,这在单元测试期间不可用。与HttpContext.Current静态控制器紧密耦合会导致代码很难测试。尽量避免与静态调用耦合。

旁注:为您的代码设置单元测试有困难是一个明确的迹象,表明它需要进行审查并且很可能需要重构。

AbstractFormsAuthentication调用它们自己的关注点/接口,以便它们可以被模拟。

public interface IFormsAuthenticationService {

    void SignOut();

    //...other code removed for brevity
}
Run Code Online (Sandbox Code Playgroud)

生产代码可以包装实际的调用,它应该可以正常工作HttpContext.Current。确保 DI 容器知道如何解决依赖关系。

public class FormsAuthenticationService : IFormsAuthenticationService {

    public void SignOut() {
        FormsAuthentication.SignOut();
    }

    //...other code removed for brevity

}
Run Code Online (Sandbox Code Playgroud)

重构控制器以依赖于抽象而不是实现问题。

public class AccountController : Controller {

    //...other code removed for brevity.

    private readonly IFormsAuthenticationService formsAuthentication;

    public AccountController(IFormsAuthenticationService formsAuthentication) {
        //...other arguments removed for brevity
        this.formsAuthentication = formsAuthentication;
    }

    public ActionResult Login(CutomerModel obj) {
        formsAuthentication.SignOut();
        //...
        return View();
    }

    //...other code removed for brevity.
}
Run Code Online (Sandbox Code Playgroud)

和示例测试

注意:我使用Moq模拟依赖项,使用FluentAssertions来断言结果。

[TestMethod]
public void LoginTest() {
    //Arrange
    var model = new CutomerModel() { Login = "admin", Password = "Password1" };        
    var mockFormsAuthentication = new Mock<IFormsAuthenticationService>();

    var controller = new AccountController(mockFormsAuthentication.Object);

    //Act
    var actual = controller.Login(model) as ViewResult;

    //Assert (using FluentAssertions)
    actual.Should().NotBeNull(because: "the actual result should have the returned view");
    mockFormsAuthentication.Verify(m => m.SignOut(), Times.Once);
}
Run Code Online (Sandbox Code Playgroud)