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
}
如何测试此代码?我需要做的是以某种方式设置租户属性,但我不能,因为:

  • 这是受保护的财产,而且
  • 它有一个私人setter
  • 更改租户的可见性“感觉”不正确。除了单元测试我的派生控制器之外,我还有什么选择?

    使用反射

    类似的方法应该可以工作(未经测试):


    我会这样做:

    在测试项目中创建一个具体的类,该类继承自抽象控制器,并将租户作为公共属性公开。在测试项目中使用模拟或虚拟代码并没有什么错(当然,只方便测试的虚拟代码永远不应该出现在生产项目中)


    如果这不适合你的情况;您可以使用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);