如何使这个ASP.NET MVC控制器更可测试?

时间:2010-06-09 17:52:10

标签: asp.net-mvc unit-testing controller

我有一个覆盖OnActionExecuting的控制器并做了类似的事情:

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);
        }
    }
}

Tenant是受保护的属性,具有私有的setter。该类本身是我的真实控制器派生的抽象基本控制器。我在其他控制器中的代码看起来很像这样:

if (Tenant == null)
{
   // Do something
}
else
{
   // Do something else
}

如何测试此代码?我需要做的是以某种方式设置租户属性,但我不能因为:

  1. 这是受保护的财产,
  2. 它有私人二传手
  3. 更改Tenant的可见性并不“感觉”正确。我对派生控制器进行单元测试的替代方法是什么?

6 个答案:

答案 0 :(得分:1)

我将如何做到这一点:

在测试项目中创建一个具体类,它继承自抽象控制器,并将Tenant公开为公共属性。在测试项目中使用模拟或虚拟代码没有任何问题(当然,只是简化测试的虚拟代码,绝不应该在生产项目中)。

如果这不适合你的情况;您可以使用Visual Studio的Private Accessor Assembly功能生成一个可以访问租户属性的类型。它在IDE中可用,or in command-line form

答案 1 :(得分:0)

使用反射?

这样的事情应该有效(未经测试):

  YourController.GetType()
    .GetProperty("Tenant", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(yourControllerInstance, null, theTestValue);

答案 2 :(得分:0)

您可以在抽象基础上创建一个方法,仅用于单元测试。您可以将其设为公开或内部,并在MVC项目上设置[assembly: InternalsVisibleTo("Tests")],如this

internal void SetTenant(Tenant testValue)
{
    Tenant = testValue;
}

答案 3 :(得分:0)

我知道你想测试从你的基类继承的具体控制器。 看起来您想要存根您的仓库以返回与您的测试环境相对应的租户 您的需求:

  

我需要做的是以某种方式设置   租户属性

从您的实施中,租户是由回购提供的

  

this.Tenant = repo.FindOne(t => t.Domain == tenantDomain);

你需要的是能够以某种方式注入你的回购,并从名称“repo”注入,这听起来像是一件好事。
这有意义吗?

- Dom

答案 4 :(得分:0)

在重新阅读@driis回答之后,我认为有更明确的方法可以做到这一点但是需要在控制器工厂中使用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这样的假类来处理空案例。

答案 5 :(得分:0)

有两种方法可以实现这个目标

  1. 模拟租户资产(标准方式)

    Mock<TenantType> tenantMock = new Mock<TenantType>();    
    var controller = new TargetController();    
    Controller.Tenant = tenantMock.Object;
    
  2. 使用反射来设置私有属性(对于那些不熟悉模拟框架的人)

  3. 如,

        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);
    

    希望这有帮助,

    谢谢, 维杰。