技术开发 频道

使用Abstractions增强ASP.NET应用程序的可测试性

 AuthorizedHandler

 这个例子就复杂些了,并且直接来源于老赵以前的某个项目的代码——当然现在为了示例进行了简化和改造。在项目中我们往往要编写一些Handler来处理客户端的请求,而同时Handler需要对客户端进行身份验证及基于角色的授权,只有特定角色的客户才能访问Handler的主体逻辑,否则便抛出异常。而这样的逻辑有其固有的结构,因此我们这类Handler编写一个公用的父类,这样我们便可使用“模板方法”的形式来补充具体逻辑了。这个父类的实现如下:

 public abstract class AuthorizedHandler : IHttpHandler

 {

 public bool IsReusable { get { return false; } }

 void IHttpHandler.ProcessRequest(HttpContext context)

 {

 this.ProcessRequest(new HttpContextWrapper(context));

 }

 internal void ProcessRequest(HttpContextBase context)

 {

 if (!context.User.Identity.IsAuthenticated)

 {

 throw new UnauthorizedAccessException();

 }

 foreach (var role in this.AuthorizedRoles)

 {

 if (context.User.IsInRole(role))

 {

 this.ProcessRequestCore(context);

 return;

 }

 }

 throw new UnauthorizedAccessException();

 }

 protected internal abstract void ProcessRequestCore(HttpContextBase context);

 protected internal abstract IEnumerable<string> AuthorizedRoles { get; }

 }

 一般来说,我们会在IHttpHandler.ProcessRequest方法中进行逻辑实现,但是我们现在直接把方法调用转发给接受HttpContextBase作为参数的ProcessRequest方法重载。HttpContextBase是一个抽象类型,这便是我们的测试目标。这个方法首先判断用户是否经过认证,然后再将用户的角色,与AuthorizedRoles抽象属性中表示的合法角色进行匹配,如果匹配成功则调用ProcessRequestCore抽象方法,而无论是用户认证还是授权失败,都会抛出UnauthorizedAccessException异常。

 这里有一个题外话:不知您是否注意到,这里没有private方法,所有的方法都有internal修饰。这么做的原因完全是为了进行单元测试。由于private方法无法被外部项目调用,因此我们只能使用internal作为修饰符,再为程序集加上InternalVisibleToAttribute标记,把所有的internal成员向测试项目开放。当然,此时程序集内部就能够随意调用那些方法了——还好,都是自家人,注意点便是了。

 这段逻辑需要测试的环节比较多,我们依次看一下:

 [TestMethod()]

 [ExpectedException(typeof(UnauthorizedAccessException))]

 public void ProcessRequestTest_Nonauthenticated_Request()

 {

 Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);

 mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(false);

 Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };

 mockHandler.Setup(h => h.ProcessRequestCore(It.IsAny<HttpContextBase>()))

 .Throws(new Exception("ProcessRequestCore should not be called."));

 mockHandler.Setup(h => h.AuthorizedRoles)

 .Throws(new Exception("AuthorizedRoles should not be accessed."));

 mockHandler.Object.ProcessRequest(mockContext.Object);

 }

 这是对没有通过身份验证的请求的回应,我们设置HttpContext.User.Identity.IsAuthenticated属性为false,并且声明不能碰触到ProcessRequestCore和AuthroizedRoles属性。在这样的情况下,我们自然期望抛出UnauthorizedAccessException。

 [TestMethod()]

 [ExpectedException(typeof(UnauthorizedAccessException))]

 public void ProcessRequestTest_Nonauthorized_Request()

 {

 Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);

 mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);

 mockContext.Setup(c => c.User.IsInRole(It.IsAny<string>()))

 .Returns(false).Verifiable();

 Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };

 mockHandler.Setup(c => c.ProcessRequestCore(It.IsAny<HttpContextBase>()))

 .Throws(new Exception("ProcessRequestCore should not be called."));

 mockHandler.Setup(c => c.AuthorizedRoles)

 .Returns(new string[] { "admin", "user" }).Verifiable();

 try

 {

 mockHandler.Object.ProcessRequest(mockContext.Object);

 }

 catch

 {

 throw;

 }

 finally

 {

 mockContext.Verify();

 mockHandler.Verify();

 }

 }

 这是测试身份验证通过,而基于角色的授权失败时的情况。我们把IsAuthenticated设为true,并且要求IsInRole方法在“接受到任何string类型参数”的时候都返回false,而最后再“象征性”地设置AuthorizedRoles所返回的内容。这个测试的期望是抛出UnauthorizedAccessException,不过值得注意的是,我们的代码还有其他要求,那就是要求IsInRole和AuthorizedRoles一定要调用过——您明白了吗?这就是为什么对Mock对象追加Verifiable和Verify方法,并且使用try/catch/finally的缘故。

 [TestMethod()]

 public void ProcessRequestTest_Authorized_Request()

 {

 Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>(MockBehavior.Strict);

 mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);

 mockContext.Setup(c => c.User.IsInRole(It.IsAny<string>())).Returns(false);

 mockContext.Setup(c => c.User.IsInRole("user")).Returns(true).Verifiable();

 Mock<AuthorizedHandler> mockHandler = new Mock<AuthorizedHandler> { CallBase = true };

 mockHandler.Setup(c => c.ProcessRequestCore(It.IsAny<HttpContextBase>()))

 .AtMostOnce().Verifiable();

 mockHandler.Setup(c => c.AuthorizedRoles).Returns(new string[] { "admin", "user" })

 .Verifiable();

 mockHandler.Object.ProcessRequest(mockContext.Object);

 mockHandler.Verify();

 mockContext.Verify();

 }

 最后的测试自然是正常流程的测试。在这里我们要检验的是正常情况下ProcessRequestCore是否“被调用,而且只被调用了一次”。如果您能够理解前两个测试,这个测试应该也同样简单才是。

0
相关文章