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.
ViewBag.Name = Resources.Resource1.Name;
You will encounter an exception like following.
{"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.
public interface IResourceProvider
{
T GetGlobalResoceValue<T>(string resourceFile, string resourceKey);
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
public class ResourceProvider : IResourceProvider
{
public T GetGlobalResoceValue<T>(string resourceFile, string resourceKey)
{
return (T)HttpContext.GetGlobalResourceObject(resourceFile, resourceKey);
}
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.
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.
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.
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.
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
HomeController controller = new HomeController(resourceProvider.Object);
This way you can easily perform unit testing on your localized web applciations.