Asp.net mvc 如何使此ASP.NET MVC控制器更易于测试?
我有一个控制器,它覆盖执行的操作,并执行如下操作:Asp.net mvc 如何使此ASP.NET MVC控制器更易于测试?,asp.net-mvc,unit-testing,controller,Asp.net Mvc,Unit Testing,Controller,我有一个控制器,它覆盖执行的操作,并执行如下操作: protected override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); string tenantDomain = filterContext.RouteData.Values["tenantDomain"] as string; if (!string
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string tenantDomain = filterContext.RouteData.Values["tenantDomain"] as string;
if (!string.IsNullOrWhiteSpace(tenantDomain))
{
using (var tx = BeginTransaction())
{
this.Tenant = repo.FindOne(t => t.Domain == tenantDomain);
}
}
}
if (Tenant == null)
{
// Do something
}
else
{
// Do something else
}
Tenant
是一个带有私有setter的受保护属性。类本身是一个抽象的基本控制器,我的真实控制器就是从它派生的。我在其他控制器中有类似的代码:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string tenantDomain = filterContext.RouteData.Values["tenantDomain"] as string;
if (!string.IsNullOrWhiteSpace(tenantDomain))
{
using (var tx = BeginTransaction())
{
this.Tenant = repo.FindOne(t => t.Domain == tenantDomain);
}
}
}
if (Tenant == null)
{
// Do something
}
else
{
// Do something else
}
如何测试此代码?我需要做的是以某种方式设置租户属性,但我不能,因为:
我会这样做: 在测试项目中创建一个具体的类,该类继承自抽象控制器,并将租户作为公共属性公开。在测试项目中使用模拟或虚拟代码并没有什么错(当然,只方便测试的虚拟代码永远不应该出现在生产项目中)
如果这不适合你的情况;您可以使用VisualStudio的私有访问器程序集功能生成一个类型,您可以在其中访问租户属性。它在IDE中可用。您可以在抽象基础上创建一个方法,仅用于单元测试。您可以将其公开或内部,并在MVC项目上设置
[assembly:InternalsVisibleTo(“Tests”)]
,如
我知道您想要测试从基类继承的具体控制器。 看起来您希望存根您的回购协议,以返回与测试上下文对应的租户。
你的需要: 我需要做的是以某种方式设置 承租人财产 从您的实施来看,承租人由回购协议提供
this.Tenant=repo.FindOne(t=>t.Domain==tenantDomain)代码>
您需要的是能够以某种方式注入您的回购协议,并且从“回购”这个听起来不错的名称开始。
这有意义吗
--
Dom在重新阅读@driis answer之后,我认为有一种更明确的方法可以做到这一点,但它需要在控制器工厂中使用DI。(网上有很多这样做的例子。)
首先,我们将封装如何从路由参数获取租户的逻辑
public class TenantFinder : ITenantFinder
{
// todo DI for repo
public Tenant Find(string tenantDomain)
{
if (string.IsNullOrWhiteSpace(tenantDomain))
{
return null;
}
using (var tx = BeginTransaction())
{
return repo.FindOne(t => t.Domain == tenantDomain);
}
}
}
然后我们可以将这个类注入控制器。(Icky部分将构造函数参数放在所有地方!)因此在抽象控制器中:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string tenantDomain = filterContext.RouteData.Values["tenantDomain"] as string;
Tenant = Finder.Find(tenantDomain );
}
然后在单元测试中,您可以创建ITenantFinder
的存根,以注入您想要使用的租户。您甚至可以创建一个伪类,如NullTenantFinder
来处理空情况。有两种方法可以实现这一点
模拟租户财产(标准方式)
消费代码
this.SetPrivateSetPropertyValue(TenantType.GetType(),controller , "Tenant",tenantInstance);
希望这有帮助
谢谢,
Vijay。但是他正在测试的控制器必须从中继承。@Ryan,当我阅读问题时,他想测试抽象控制器代码?如果不是这样的话,那么他可以模仿他正在测试的控制器。您测试抽象代码是正确的。他还询问如何在实际控制器中测试他的if(Tenant==null)
代码。为此,他需要一种方法来确定承租人。我想他可以在每个测试中存根一些路由数据并调用OnActionExecuting
,但在我看来,它不像内部方法那样明确(设置方面)。请参阅下面我的第二个答案以获得非常明确的解决方案。这是正常的做法吗?我可以理解为什么测试抽象类需要它,但是当我测试我的具体控制器时,我更喜欢“按原样”测试它们,而不是从它们派生并测试派生类。使用专用访问器程序集比自己编写反射代码要容易得多。除非复制/粘贴/稍微修改上述代码;)我的观点是:你是对的,创建模拟或伪代码(因为它在生产代码之外)不是一件坏事,反射解决方案也是如此(你可能应该重构以供重用)。这似乎是最干净的方法。我有一种感觉,即使我使用Ryan建议的TenantFinder方法,我仍然需要在测试派生控制器时使用反射来调用OnActionExecuting方法。我不太喜欢在实现中添加任何东西来支持单元测试。如果添加了SetTenant方法,就会有人出现并在某个时候滥用它。它打破了灌木丛。我同意德里斯的观点。更改字段的可见性只是为了使其可测试不是我想做的事情。Ryan清理了抽象类中的代码,该代码可能会在以下测试中失败“OnActionExecuting应该从TenantService获取filterContext中tenantDomain的租户”。测试可能会失败,因为if(!string.IsNullOrWhiteSpace(tenantDomain))
和他的NullTenant对象绝对是一个很好的idee,它将删除if tenant==nullI。我真的不需要存根我的存储库。我正在测试中存根我的数据库,因此存储库工作正常。然后,您可以使用“正确”值设置filterContext.RoutedData.Values[“tenantDomain”],但存根repo的目的是控制“repo.FindOne(t=>t.Domain==tenantDomain);”在测试中,尽可能靠近测试。为了测试您的用例,您可以有两个小存根repoStubReturnsMyDomain和repostubReturnsAll,它们总是提供可预测的数据。我喜欢这种方法。我已经为我的控制器连接了DI,所以这很容易采用,但是当我测试我的具体控制器时,我如何调用OnActionExecuting呢?反射?另一方面,这是ac
private static void SetPrivateSetPropertyValue(Type type, object obj, string fieldName, object value)
{
PropertyInfo propInfo = type.GetProperty(fieldName);
propInfo.SetValue(obj, value, null);
}
this.SetPrivateSetPropertyValue(TenantType.GetType(),controller , "Tenant",tenantInstance);