Click here to Skip to main content
15,881,044 members
Articles / Web Development / ASP.NET
Tip/Trick

Unit testing MVC controllers which are using global resources (App_GlobalResources)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
29 Jun 2014CPOL3 min read 24.5K   6   4
Unit testing the MVC controller actions which are using Global or Local resources. As this is not straight forward to Mock the static properties of the resource files.

Introduction

One of the main benefits of ASP.NET MVC is the ease of unit testing. We can execute controller methods (Actions) in complete isolation within the test framework. In a similar way we can test our service (business layer) independent of the DbContext/Repository.

Main idea behind the unit testing is to create a program which will make certain assumptions about a specific piece of code in our application and determine if, based on those assumptions, the code generates the expected results or not.

In this article, I am not explaining the entire MVC testing but taking one case where most of the developers face prolem while testing controllers.

Problem

When you are directly using App_GlobalResources resource file in your MVC controller code, it will break your unit test cases, as the global resource files are not available for the unit test case exection. When the application runs through browser a separate dll for the App_GlobalResource is being created by the ASP.NET runtime. The required resource dll will not be available without an ASP.NET compilation which leads to the UTC failures.

When you test any of your controller action, which is using a global resource file as shown in the following code snippet.

C++
ViewBag.Name = Resources.Resource1.Name;

You will encounter an exception like following.

Image 1

{"Could not load file or assembly 'App_GlobalResources' or one of its dependencies. The system cannot find the file specified.":"App_GlobalResources"}

Above error message clearly explains that while running the unit test cases the App_GlobalResources is not available.

Solution 1

To fix this issue quickly you can move the resources outside the App_GlobalResources folder to a different folder and make them as embedded resources to the project library. But this approach have a flaw that every time a resource value gets changes entire dll needs to be re-build and re-deployed which is not acceptable in most of the scenarios.

You can get more details on this approach here http://odetocode.com/Blogs/scott/archive/2009/07/16/resource-files-and-asp-net-mvc-projects.aspx

Solution 2 (my way)

To fix this problem you can create a ResourceProvider class which will be available to the controller through the DI or as a base class property, and instead of directly using the Resource class you will use your custom ResourceProvider to get the resource values in the controller.

 To make your ResourceProvider decoupled, you should use an interface which will work as a proxy for the resource provider and will help if in future you want to change the ResourceProvider implementation without changing the controller logic, but the main benefit you will get from the interface is that you can mock the ResourceProvider for your unit testing.

IResourceProvider : Following is the code for our IResourceProvider implementation.

C#
/// <summary>
/// App_Global and App_Local Resource Provider.
/// </summary>
public interface IResourceProvider
{
    /// <summary>
    /// To get the Global Resources from a file on the basis of provided key
    /// </summary>
    /// <typeparam name="T">Value Type</typeparam>
    /// <param name="resourceFile">Class Name</param>
    /// <param name="resourceKey">Key Name</param>
    /// <returns></returns>
    T GetGlobalResoceValue<T>(string resourceFile, string resourceKey);

    /// <summary>
    /// To get the Local Resources from a file on the basis of provided key
    /// </summary>
    /// <typeparam name="T">Value Type</typeparam>
    /// <param name="resourceFile">Class Name</param>
    /// <param name="resourceKey">Key Name</param>
    /// <returns></returns>
    T GetLocalResoceValue<T>(string resourceFile, string resourceKey);
}

 

ResourceProvider : Following is the implementation of the default ResourceProvider, which is reading from the Global and Local resources from the HttpContext

C#
public class ResourceProvider : IResourceProvider
{
    /// <summary>
    /// Get the Global Resource Values from the App_Global_Resources
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="resourceFile">File Name</param>
    /// <param name="resourceKey"> Key Name</param>
    /// <returns></returns>
    public T GetGlobalResoceValue<T>(string resourceFile, string resourceKey)
    {
        return (T)HttpContext.GetGlobalResourceObject(resourceFile, resourceKey);
    }

    /// <summary>
    /// Get the Local Resources
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="resourceFile"></param>
    /// <param name="resourceKey"></param>
    /// <returns></returns>
    public T GetLocalResoceValue<T>(string resourceFile, string resourceKey)
    {
        return (T)HttpContext.GetLocalResourceObject(resourceFile, resourceKey);
    }
}

 

Q: How the resource provider will be available in the controller?

Resource provider can be injected to the constructor using a DI or it can be made available to all the controllers by a base class property.

Following code shows how the ResourceProvider can be injected using the constructor.

C#
public class HomeController : Controller
{
    IResourceProvider _resourceProvider;
    public HomeController()
    {
        _resourceProvider = new ResourceProvider();
    }
    public HomeController(IResourceProvider resourceProvider)
    {
        _resourceProvider = resourceProvider;
    }

    public ActionResult About()
    {

        ViewBag.Message =
         _resourceProvider.GetGlobalResoceValue<string>("Resource1",
                                      "Name");

        return View();
    }

}

 

Q: How the resource provider instance will be passed to the controller?

If you are using any DI container like StrucutreMap, so you need to tell your DI container how to resolve ResourceProvider and call the appropriate constructor to instantiate the controller class.

Following line needs to be added in the Ioc class in case you are using SturucutreMap to resolve the correct ResourceProvider. For other DI containers you can use different syntax to tell the container about the type.

C#
x.For<IResourceProvider>().Use<ResourceProvider>();

Once you are done with the above changes you can now easily mock the ResourceProvider from the controller test.

Note: google for MOQ, if you are not aware of the mocking frameworks.

Following are the steps to be used for executing controller unit test cases.

1- Create the mock of IResourceProvider interface.

C#
Mock<IResourceProvider> resourceProvider = new Mock<IResourceProvider>();

2 - Setup the methods which are being used in the action method for getting the resource values as following.

C#
resourceProvider.Setup(x=>
      x.GetGlobalResoceValue<string>
      (It.IsAny<string>(),It.IsAny<string>()))
      .Returns("This is a dummy resource message");

 

3- Create the controller instance and pass the proxy ResourceManager and continue your testing

C#
HomeController controller = new HomeController(resourceProvider.Object);

This way you can easily perform unit testing on your localized web applciations.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
Its me Smile | :)

Comments and Discussions

 
QuestionNicely done :) Pin
Asolvent10-Nov-16 14:49
Asolvent10-Nov-16 14:49 
Question"The relative virtual path 'DepartmentsAdmin' is not allowed here." Pin
Gary In SD28-Nov-14 11:51
Gary In SD28-Nov-14 11:51 
QuestionExpaling? Pin
B. Clay Shannon27-Jun-14 5:22
professionalB. Clay Shannon27-Jun-14 5:22 
AnswerRe: Expaling? Pin
PSK_29-Jun-14 17:57
PSK_29-Jun-14 17:57 
ha ha.. corrected now.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.