Ragesh
Ragesh

Reputation: 3063

How do I make this ASP.NET MVC controller more testable?

I have a controller that overrides OnActionExecuting and does something like this:

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 is a protected property with a private setter. The class itself is an abstract base controller that my real controllers derive from. I have code in other controllers that looks a lot like this:

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

How do I test this code? What I need to do is to somehow set the Tenant property, but I can't because:

  1. It's a protected property, and
  2. It has a private setter

Changing the visibility of Tenant doesn't "feel" right. What are my alternatives to unit test my derived controllers?

Upvotes: 5

Views: 523

Answers (6)

vijaysylvester
vijaysylvester

Reputation: 5010

There are two ways to accomplish this

  1. Mock the Tenant Property (Standard Way)

    Mock<TenantType> tenantMock = new Mock<TenantType>();    
    var controller = new TargetController();    
    Controller.Tenant = tenantMock.Object;
    
  2. Use reflection to set the private property (for those who are not familiar with mocking frameworks)

E.g.,

    private static void SetPrivateSetPropertyValue(Type type, object obj, string fieldName, object value)
    {
        PropertyInfo propInfo = type.GetProperty(fieldName);
        propInfo.SetValue(obj, value, null);
    }

Consuming Code

this.SetPrivateSetPropertyValue(TenantType.GetType(),controller , "Tenant",tenantInstance);

Hope this helps,

Thanks , Vijay.

Upvotes: 0

Ryan
Ryan

Reputation: 4313

Ok after re-reading @driis answer I think there is a more explicit way to do this but it will require using DI in your controller factory. (There are many samples of how to do this online.)

First we are going to encapsulate the logic of how to get a Tenant from a route parameter.

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

Then we can inject this class into the controller. (Icky part is putting the constructor parameter everywhere!) So in the abstract controller:

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    string tenantDomain = filterContext.RouteData.Values["tenantDomain"] as string;
    Tenant = Finder.Find(tenantDomain );
}

Then in your unit test you can create a stub of ITenantFinder to inject the tenant you want to use. You could even make a fake class like NullTenantFinder to handle the null cases.

Upvotes: 0

Dom Ribaut
Dom Ribaut

Reputation: 308

I understand that you want to test the concrete controllers that inherits from your base class. It looks like you want want to stub your repo to return the tenant corresponding to the context of your test.
Your need:

What I need to do is to somehow set the Tenant property

And from your implementation the tenant is provide by the repo

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

What you need is to be able to inject your repo by some way and from the name "repo" that sounds like an ok thing.
does that make sense?

-- Dom

Upvotes: 0

driis
driis

Reputation: 164281

Here's how I would do it:

Create a concrete class in your test project, that inherits from your abstract controller, and exposes Tenant as a public property. There is nothing wrong with having mock or dummy code in the test project (and of course, dummy code that just facilitates testing, should never be in the production projects).

If that is not a good fit for your situation; you could use Visual Studio's Private Accessor Assembly feature to generate a type where you can access your Tenant property. It is available in the IDE, or in command-line form.

Upvotes: 1

Ryan
Ryan

Reputation: 4313

You could create a method on the abstract base for use only in unit testing. You can either make it public or internal and set [assembly: InternalsVisibleTo("Tests")] on the MVC project like this.

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

Upvotes: 0

Koen
Koen

Reputation: 3686

Using reflection?

Something like this should work (not tested):

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

Upvotes: 0

Related Questions