Sunday, March 20, 2011

Write unit test for a method depends on RoleProvider

Let's say I have a class like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return string.IsNullOrEmpty(Roles) 
            ? base.AuthorizeCore(httpContext) 
            : IsUserInRole(Roles);
    }

    public static bool IsUserInRole(string roles)
    {
        return roles.Split(',', ';').Any(x => System.Web.Security.Roles.IsUserInRole(x.Trim()));
    }
}
This class is a very simple class that depends on a RoleProvider to check whether current user is in one of demanded roles. The question is what I'm suppose to test this class? Hmmm, the System.Web.Security.Roles class has a Providers collection of RoleProvider but it's readonly as well as the property Provider which should be default provider. It looks like there is only one way to put my mock/fake role provider is placing it into configuration file of the Test project. Currently, I'm interested in using NSubstitue as the mocking framework, but I think the following TestRoleProvider can be easily changed to use other mock library like RhinoMock or Moq. Here is my very simple TestRoleProvider that can be configed in the App.config and later on arrange our "Expectation" on it:
public class TestRoleProvider : RoleProvider
{
    private readonly RoleProvider _roleProvider;
    public TestRoleProvider(RoleProvider provider)
    {
        _roleProvider = provider;
    }

    public TestRoleProvider()
    {
        _roleProvider = Substitute.For<RoleProvider>();
    }

    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        RoleProvider.AddUsersToRoles(usernames, roleNames);
    }

    public override string ApplicationName
    {
        get { return RoleProvider.ApplicationName; }
        set { RoleProvider.ApplicationName = value; }
    }

    public RoleProvider RoleProvider
    {
        get { return _roleProvider; }
    }

    public override void CreateRole(string roleName)
    {
        RoleProvider.CreateRole(roleName);
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        return RoleProvider.DeleteRole(roleName, throwOnPopulatedRole);
    }

    public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    {
        return RoleProvider.FindUsersInRole(roleName, usernameToMatch);
    }

    public override string[] GetAllRoles()
    {
        return RoleProvider.GetAllRoles();
    }

    public override string[] GetRolesForUser(string username)
    {
        return RoleProvider.GetRolesForUser(username);
    }

    public override string[] GetUsersInRole(string roleName)
    {
        return RoleProvider.GetUsersInRole(roleName);
    }

    public override bool IsUserInRole(string username, string roleName)
    {
        return _roleProvider.IsUserInRole(username, roleName);
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
        RoleProvider.RemoveUsersFromRoles(usernames, roleNames);
    }

    public override bool RoleExists(string roleName)
    {
        return RoleProvider.RoleExists(roleName);
    }
}
And here is the configuration in App.config to make the test project use this TestRoleProvider as default RoleProvider:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <roleManager enabled="true" defaultProvider="TestRoleProvider">
      <providers>
        <add name="TestRoleProvider" type="MyCompany.MyProject.Tests.TestRoleProvider, MyCompany.MyProject.Tests"/>
        <!-- 
            Note: Change the namespace, assembly name to the assembly you put TestRoleProvider.
        -->
      </providers>
    </roleManager>
  </system.web>
</configuration>
Now, I will write a simple unit test for method CustomAuthorizeAttribute.IsUserInRole(roles):
[TestMethod]
public void Can_Check_User_Roles_Using_Role_Provider()
{
    // Arrange
    var testRoleProvider = Roles.Provider as TestRoleProvider;
    if (testRoleProvider == null)
    {
        throw new Exception("TestRoleProvider must be configed in app.config as default provider for your unit tests");
    }
    testRoleProvider.RoleProvider.IsUserInRole(Arg.Any<string>(), Arg.Any<string>()).Returns(true);
            
    // Action
    var result = CustomAuthorizeAttribute.UserInRole("Admin, Mod");

    // Assert
    Assert.AreEqual(true, result);
}
And here the result after running above unit test: TestFailed Look like something's wrong. I swear I had setup the provider correctly in App.config. The Test ran failed and that means "testRoleProvider" is not null because I did not receive any exception. Maybe there are some secrets inside the implementation of System.Web.Security.Roles. Fine, I will find out what they are. It's time to use the power of Reflector but let's download it first. Hmm, newest version is 7 and it has not been free anymore. Ok, I will use previous free version, let's show some googling skill. Hehe, I found some old web addresses that allow me to download Reflector 5.x. Arggg, It forced me to upgrade to 7.0, I chose not to upgrade and It deleted the reflector executable file. All I need is to see the code inside Roles, so free 14 days trial is enough for me :(. And here is the secret:
public static bool IsUserInRole(string username, string roleName)
{
    bool flag3;
    if (HostingEnvironment.IsHosted && EtwTrace.IsTraceEnabled(4, 8))
    {
        EtwTrace.Trace(EtwTraceType.ETW_TYPE_ROLE_BEGIN, HttpContext.Current.WorkerRequest);
    }
    EnsureEnabled();
    bool flag = false;
    bool flag2 = false;
    try
    {
        SecUtility.CheckParameter(ref roleName, true, true, true, 0, "roleName");
        SecUtility.CheckParameter(ref username, true, false, true, 0, "username");
        if (username.Length < 1)
        {
            return false;
        }
        IPrincipal currentUser = GetCurrentUser();
        if (((currentUser != null) && (currentUser is RolePrincipal)) && ((((RolePrincipal) currentUser).ProviderName == Provider.Name) && StringUtil.EqualsIgnoreCase(username, currentUser.Identity.Name)))
        {
            flag = currentUser.IsInRole(roleName);
        }
        else
        {
            flag = Provider.IsUserInRole(username, roleName);
        }
        flag3 = flag;
    }
    finally
    {
        if (HostingEnvironment.IsHosted && EtwTrace.IsTraceEnabled(4, 8))
        {
            if (EtwTrace.IsTraceEnabled(5, 8))
            {
                string str = SR.Resources.GetString(flag ? "Etw_Success" : "Etw_Failure", CultureInfo.InstalledUICulture);
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_ROLE_IS_USER_IN_ROLE, HttpContext.Current.WorkerRequest, flag2 ? "RolePrincipal" : Provider.GetType().FullName, username, roleName, str);
            }
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_ROLE_END, HttpContext.Current.WorkerRequest, flag2 ? "RolePrincipal" : Provider.GetType().FullName, username);
        }
    }
    return flag3;
}
And this is the way it gets current user:
private static IPrincipal GetCurrentUser()
{
    if (HostingEnvironment.IsHosted)
    {
        HttpContext current = HttpContext.Current;
        if (current != null)
        {
            return current.User;
        }
    }
    return Thread.CurrentPrincipal;
}
Okey, i have enough information to let the test use my roleprovider. It's quite complicated to modify HostingEnvironment.IsHosted as well as HttpContext.Current. Indeed, I can create a mock httpContext and set the mock object to HttpContext.Current but I think changing CurrentPrincipal of current Thread is more simple. So, I decided to change my unit test arrangement:
[TestMethod]
public void Can_Check_User_Roles_Using_Role_Provider()
{
    // Arrange
    var identity = Substitute.For<IIdentity>();
    identity.IsAuthenticated.Returns(true);
    identity.Name.Returns("AnyUsername");

    var principal = Substitute.For<IPrincipal>();
    principal.Identity.Returns(identity);
    Thread.CurrentPrincipal = principal;

    var testRoleProvider = Roles.Provider as TestRoleProvider;
    if (testRoleProvider == null)
    {
        throw new Exception("TestRoleProvider must be configed in app.config as default provider for your unit tests");
    }
    testRoleProvider.RoleProvider.IsUserInRole(Arg.Any<string>(), Arg.Any<string>()).Returns(true);

    
    // Action
    var result = CustomAuthorizeAttribute.IsUserInRole("Admin, Mod");


    // Assert
    Assert.AreEqual(true, result);
}
Hoho, finally It passed. Reflector is so useful, isn't it? I think we can do the same way if we want to test any implementations that need MembershipProvider or ProfileProvider. I would move the Principal Arrangement into a public static method of TestRoleProvider to make it reusable. And because I changed the Thread.CurrentPrincipal during the test, so it could be nice if we make a test initialize and test cleanup to backup/restore current principle.But I let you to do those stuff if you're interested in :D Cheers.

Wednesday, March 16, 2011

Turn on Compile-time View Checking for ASP.NET MVC Projects

You can make the web project perform compile time checking of the markup in the views by modifying the .csproj file and turning on the "MvcBuildViews" property. The advantage of this is that you will know at compile time any problem with a view, not at run time when someone accesses that page. So please follow the below blog to do that: http://blogs.msdn.com/b/jimlamb/archive/2010/04/20/turn-on-compile-time-view-checking-for-asp-net-mvc-projects-in-tfs-build-2010.aspx However, doing in this will make the compilation is extremely slower. It made me crazy every time I want to debug a unit test because it build the solution include the views and It could take 1 minutes before starting the test debug. To overcome that, I turn off MvcBuildViews for Debug compilation of the solution. To do this, open the web .csproj file by any text editor, find <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> then add <MvcBuildViews>false</MvcBuildViews> under it. It will simply overwrite the default setting which was set to true for DEBUG configuration.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <MvcBuildViews>false</MvcBuildViews>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
</PropertyGroup>
Cheers

Thursday, March 10, 2011

Make class visible to only test assemblies

Yesterday, i was spending my time at work trying to increase the test coverage for our solutions. It's such a hard work because everone wants to write unit tests, everyone want to have better coverage but all people seem to have a lot of stuff to finish before deadline. We talked about it every sprint retrospective and the coverage started to increase very slowly from last week. Hmmm, I always want to be in a TDD team but never had that chance. However, we accept the fact that we could implement the user story first write unit test for what we did and .... wait for bugs :D. It's given that we'd better have unit tests rather than have nothing. Now, the effort i spent was 3 hours that produced 27 unit tests which was my ambition to cover 100% a class which has 1 single method of about 50 lines of code. The reason for that big number of unit tests is that my method had so many cases. Well, it actually cover about 9x%, not really 100% as I said but the test does visit every single line of my class. After checking code in, I was quite happy with what the result and decided to update my twitter status. Right after 10 seconds, my colleague said: "WTF is that method?" Hmm, now look at the original method:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    var model = filterContext.Controller.ViewData.Model as BusinessViewModel;
    if (model != null && !filterContext.Controller.ViewData.ModelState.IsValid && !GetModelUpdatedState(filterContext))
    {
        if ((model.Categories == null || model.Categories.Count == 0) &&
            (model.CategoryIds != null && model.CategoryIds.Count > 0))
        {
            var cats = ServiceFactory.BusinessService.GetBusinessCategoriesByIds(model.CategoryIds).ToList();
            model.Categories = Mapper.Map<List<BusinessCategory>, List<BusinessCategoryViewModel>>(cats);
        }

        if ((model.Features == null || model.Features.Count == 0) &&
            (model.FeatureTitles != null && model.FeatureTitles.Count > 0))
        {
            var features = ServiceFactory.BusinessService.GetBusinessFeaturesByTitles(model.FeatureTitles).ToList();
            model.Features = Mapper.Map<List<BusinessFeature>, List<BusinessFeatureViewModel>>(features);
        }

        if ((model.Tags == null || model.Tags.Count == 0) &&
            (model.TagNames != null && model.TagNames.Count > 0))
        {
            var tags = ServiceFactory.BusinessService.GetBusinessTags(model.TagNames).ToList();
            model.Tags = Mapper.Map<List<BusinessTag>, List<TagViewModel>>(tags);
        }

        if ((model.Owners == null || model.Owners.Count == 0) &&
            (model.OwnersIds != null && model.OwnersIds.Count > 0))
        {
            var owner = ServiceFactory.IdentityService.GetById(model.OwnersIds[0]);
            if (owner != null) model.Owners = new List<BusinessOwnerViewModel> { new BusinessOwnerViewModel { Id = owner.Id, Email = owner.Email } };
        }

        SetModelUpdatedState(filterContext, true);
    }

    base.OnActionExecuted(filterContext);
}
There are quite alot of cases, isn't it. I think if I want to cover 100% of this one, I should have written 6 + 8 + 8 + 8 + 10 unit tests. My colleague raised a concern about this method is very hard to maintain. If someone changes some small thing, it could break my tests. Indeed, there is a way to break this method into smaller methods that would be very easier to test and maintain. I was suggested to split this method into smaller internal methods and use [InternalsVisibleTo] to make those internal methods visible to my test assembly. So my new methods are:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    var model = filterContext.Controller.ViewData.Model as BusinessViewModel;
    if (model != null && !filterContext.Controller.ViewData.ModelState.IsValid && !GetModelUpdatedState(filterContext))
    {
        UpdateCategories(model);

        UpdateFeatures(model);

        UpdateTags(model);

        UpdateOwners(model);

        SetModelUpdatedState(filterContext, true);
    }

    base.OnActionExecuted(filterContext);
}

internal void UpdateOwners(BusinessViewModel model)
{
    if ((model.Owners == null || model.Owners.Count == 0) &&
        (model.OwnersIds != null && model.OwnersIds.Count > 0))
    {
        var owner = ServiceFactory.IdentityService.GetById(model.OwnersIds[0]);
        if (owner != null) model.Owners = new List<BusinessOwnerViewModel> { new BusinessOwnerViewModel { Id = owner.Id, Email = owner.Email } };
    }
}

internal void UpdateTags(BusinessViewModel model)
{
    if ((model.Tags == null || model.Tags.Count == 0) &&
        (model.TagNames != null && model.TagNames.Count > 0))
    {
        var tags = ServiceFactory.BusinessService.GetBusinessTags(model.TagNames).ToList();
        model.Tags = Mapper.Map<List<BusinessTag>, List<TagViewModel>>(tags);
    }
}

internal void UpdateFeatures(BusinessViewModel model)
{
    if ((model.Features == null || model.Features.Count == 0) &&
        (model.FeatureTitles != null && model.FeatureTitles.Count > 0))
    {
        var features = ServiceFactory.BusinessService.GetBusinessFeaturesByTitles(model.FeatureTitles).ToList();
        model.Features = Mapper.Map<List<BusinessFeature>, List<BusinessFeatureViewModel>>(features);
    }
}

internal void UpdateCategories(BusinessViewModel model)
{
    if ((model.Categories == null || model.Categories.Count == 0) &&
        (model.CategoryIds != null && model.CategoryIds.Count > 0))
    {
        var cats = ServiceFactory.BusinessService.GetBusinessCategoriesByIds(model.CategoryIds).ToList();
        model.Categories = Mapper.Map<List<BusinessCategory>, List<BusinessCategoryViewModel>>(cats);
    }
}
And i put the follwowing line to AssemblyInfo.cs

 
[assembly: InternalsVisibleTo("MyProject.Web.Mvc.Test")]
So, those internal methods are more simple than then it's easier to setup and test those things without paying any care about ViewModel, ModelState. etc because they would be tested in different tests. The complexitity is still there, but now people can changes and are not afraid to break the tests easily like before. The number of lines of my test class reduced from 600 to 500, not much actually but I like this way.

Sunday, March 6, 2011

Deploy items for unit tests

If you are running a unit test that requires an additional item like testing data, xml file, etc, you need some how to copy these files into test run folder. Executing unit test using VS.NET Testing framework is different to other unit test libraries like NUnit which runs the tests using the compiled assemblies in build folder (Debug/Release). Visual Studio creates deployment files under a folder named TestResults within the solution hierarchy. If the TestResults folder does not exist, Visual Studio will creates it. We can deploy dependent test items by different ways. In this post, i will write about the simplest way which is using DeploymentItem attribute. In this example, I want to use file "TestData.xml" in my unit test so I should decorate the test method with DeploymentItem like below:
[TestMethod]
[DeploymentItem(@"TestData.xml")]
public void Test_Something_Needs_TestData_Xml()
{
    // ...
}
One more note, to make TestData.xml available for deployment into TestResults folder, this file has to be included in the project and have its "Copy to Output Directory" property set to "Copy Always". Cheers