最近有个项目求助,由于开发人员把页面处理的相关逻辑写到了Controller层里,需要增加对Controller层的单元测试。
我看了下Controller层里难于测试的原因主要是httpcontext类难模拟。Controller层用到了session,request信息。
我的解决方法是,在httpcontext与Controller之间增加一个IHttpContextRepository接口,它负责将httpcontext里的内容封装到model里传给Controller。
那么Controller层只和Model层打交道。
定义IHttpContextRepository接口
public interface IHttpContextRepository
{
LogOnModel GetLogOnModel(HttpContextBase context);
string GetAgent(HttpContextBase context);
}
定义BaseController,所有的业务Controller派生于BaseController,BaseController内有一个依赖属性IHttpContextRepository。
public class BaseController : Controller
{
protected IHttpContextRepository httpContextRepository;
public BaseController(IHttpContextRepository prepareControllerVar)
{
httpContextRepository = prepareControllerVar;
}
}
IHttpContextRepository由依赖注入实现,为此引入了Autofac模块。
在Global.asax中,添加注册组件的代码。
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Setup AutoFac
var builder = new ContainerBuilder();
DependancySetup(builder);
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
protected void DependancySetup(ContainerBuilder builder)
{
// Controllers
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<HttpContextRepository>().AsImplementedInterfaces();
}
添加实现类
public class HttpContextRepository : IHttpContextRepository
{
public LogOnModel GetLogOnModel(HttpContextBase context)
{
LogOnModel LGmodel = new LogOnModel();
LGmodel.UserName = (string)context.Session["NAME"];
LGmodel.Password = (string)context.Session["PASSWORD"];
return LGmodel;
}
public string GetAgent(HttpContextBase context)
{
return context.Request.UserAgent;
}
}
在Controller层调用IHttpContextRepository,获取model对象
[HttpGet]
public ActionResult Top()
{
...
LogOnModel LGmodel = httpContextRepository.GetLogOnModel(this.HttpContext);
...
return View(LGmodel);
}
至此,原代码改造完毕。下面要写测试代码。
为了方便Mock,引入Moq模块。
使用了Xunit框架
public void TopTest_001()
{
var mockRepository = new Mock<IHttpContextRepository>();
LogOnModel model = new Models.LogOnModel { UserName = "tom", Password = "12345678" };
//单元测试时,我并不关心HttpContext内的内容,所以放个空的object。我关心的是返回的model
mockRepository.Setup(cr => cr.GetLogOnModel(new object() as HttpContextBase)).Returns(model);
mockRepository.Setup(cr => cr.GetPhoneFlg(new object() as HttpContextBase)).Returns("IE");
var controller = new AccountController(mockRepository.Object);
var result = controller.Top() as ViewResult;
Assert.Equal("tom", ((LogOnModel)result.ViewData.Model).UserName);
Assert.Equal("12345678", ((LogOnModel)result.ViewData.Model).Password);
}