9

I'm writing unit tests for a controller in an MVC3 web project, but my tests throw exceptions when they try and access a resource like this:

return Index(Resources.Strings.MyStringResource);

The resource is a .resx file titled Strings.

I'm using the Moq libraries to achieve unit test for HttpContextBase functionality, so I was wondering how I would go about using the Moq libraries to access an App_GlobalResource.

Any help or pointers would be greatly appreciated!

martinthebeardy
  • 742
  • 1
  • 7
  • 18

3 Answers3

5

You can't, at least not directly. The strongly-typed classes that are generated from resource (.resx) files expose static, not instance methods.

Because of this, they can't implement an interface method, nor are they virtual; Moq requires that at least one of these conditions are met in order to create a mock.

To get around this, you would create an abstraction, like anything else:

public interface IResources
{
    string MyStringResource { get; }
}

You'd pass (or inject) an implementation of this into your controller, and then pass that to your Index method. That implementation might look something like this:

public class ResourcesWrapper : IResources
{
    public string MyStringResource 
    { 
        get 
        { 
            return Resources.Strings.MyStringResource; 
        } 
    }
}

Then, when you're testing, you can use Moq to create a mock of the IResources interface, and pass that to your controller, like so:

// Create the mock.
var mock = new Mock<IResources>();

// Setup the property.
mock.SetupProperty(m => m.MyStringResource, "My Mocked Value");

// Pass the object somewhere for use.
Assert.AreEqual(mock.Object.MyStringResource, "My Mocked Value");
casperOne
  • 73,706
  • 19
  • 184
  • 253
  • Hi Casper, I've just tried implementing your answer, and it has failed, throwing an `IOException`: `"Could not load file or assembly 'App_GlobalResources' or one of its dependencies. The system cannot find the file specified.":"App_GlobalResources"` Can you provide any further tips? – martinthebeardy Oct 23 '12 at 19:12
  • @MartinBlake That sounds like a separate issue. Although the class where `ResourcesWrapper` implements `IResources` should exist in the same assembly that the resources are embedded in; those typed wrappers are marked internal.' – casperOne Oct 23 '12 at 19:20
  • check out my solution below, I'll mark your answer as the accepted one, as I'd have been nowhere without it! Thanks for your help! – martinthebeardy Oct 23 '12 at 20:21
2

So, after implementing casperOne's answer, I ran into another error:

I was presented with an IOException stating:

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

Scott Allen provided the reason and inherent solution to this problem.

So what I did was made a new resources file in a new folder named 'TResources' in my web project, named 'TResources' purely because it is a Resources folder that is only being created and used for Testing purposes (clever, eh?)

I then changed the properties of my ResourcesWrapper class to return TResources.Strings.MyStringResource rather than Resources.Strings.MyStringResource.

NOTE: The properties in the IResources interface must not be read-only, as when setting up the mock object, if the property is read-only it will fail as the value cannot be set.

Therefore, IResources should look a little something like this:

public interface IResources
{
    string MyStringResource { get; set; }
}

ResourcesWrapper should then implement IResources like this:

public class ResourcesWrapper : IResources
{
    public string MyStringResource 
    { 
        get 
        { 
            return TResources.Strings.MyStringResource; 
        } 
        set
        {
            //do nothing
        }
    }
}

So that you can then achieve a successful mock in your Unit Test, like this:

var mock = new Mock<IResources>();
mock.SetupProperty(m => m.MyStringResource, "");

NOTE: You don't have to specify anything in the initialValue parameter of the method above, as the property will be returning a value retrieved from the Strings.resx.

That concludes my question, I hope this can be helpful to someone else on internet land!

Community
  • 1
  • 1
martinthebeardy
  • 742
  • 1
  • 7
  • 18
  • Why would you add a setter to the interface? Moq absolutely allows read-only properties to have values set, but you have to call `SetProperty`. Also, this is a little bit of a separate question and answer, it doesn't directly address the question, as posted. – casperOne Oct 23 '12 at 20:54
  • Thought I may have posted it in the wrong place. Any tips on where it should go? And I had created them as read-only properties, but it threw an error, so I just shoved a setter on there. This is only a demo project so I wasn't too worried. – martinthebeardy Oct 23 '12 at 21:42
0

This response to a similar Stack Overflow question by Darin Dimitrov provides a much simpler approach. It's all about modifying the Resources.resx file properties to support unit testing.

Community
  • 1
  • 1
Bern
  • 7,808
  • 5
  • 37
  • 47