How to MOQ an Indexed property

C#TddMockingMoq

C# Problem Overview


I am attempting to mock a call to an indexed property. I.e. I would like to moq the following:

object result = myDictionaryCollection["SomeKeyValue"];

and also the setter value

myDictionaryCollection["SomeKeyValue"] = myNewValue;

I am doing this because I need to mock the functionality of a class my app uses.

Does anyone know how to do this with MOQ? I've tried variations on the following:

Dictionary<string, object> MyContainer = new Dictionary<string, object>();
mock.ExpectGet<object>( p => p[It.IsAny<string>()]).Returns(MyContainer[(string s)]);

But that doesn't compile.

Is what I am trying to achieve possible with MOQ, does anyone have any examples of how I can do this?

C# Solutions


Solution 1 - C#

It's not clear what you're trying to do because you don't show the declaration of the mock. Are you trying to mock a dictionary?

MyContainer[(string s)] isn't valid C#.

This compiles:

var mock = new Mock<IDictionary>();
mock.SetupGet( p => p[It.IsAny<string>()]).Returns("foo");

Solution 2 - C#

Ash, if you want to have HTTP Session mock, then this piece of code does the job:

/// <summary>
/// HTTP session mockup.
/// </summary>
internal sealed class HttpSessionMock : HttpSessionStateBase
{
    private readonly Dictionary<string, object> objects = new Dictionary<string, object>();

    public override object this[string name]
    {
        get { return (objects.ContainsKey(name)) ? objects[name] : null; }
        set { objects[name] = value; }
    }
}

/// <summary>
/// Base class for all controller tests.
/// </summary>
public class ControllerTestSuiteBase : TestSuiteBase
{
    private readonly HttpSessionMock sessionMock = new HttpSessionMock();

    protected readonly Mock<HttpContextBase> Context = new Mock<HttpContextBase>();
    protected readonly Mock<HttpSessionStateBase> Session = new Mock<HttpSessionStateBase>();

    public ControllerTestSuiteBase()
        : base()
    {
        Context.Expect(ctx => ctx.Session).Returns(sessionMock);
    }
}

Solution 3 - C#

As you correctly spotted, there are distinct methods SetupGet and SetupSet to initialize getters and setters respectively. Although SetupGet is intended to be used for properties, not indexers, and will not allow you handling key passed to it. To be precise, for indexers SetupGet will call Setup anyway:

internal static MethodCallReturn<T, TProperty> SetupGet<T, TProperty>(Mock<T> mock, Expression<Func<T, TProperty>> expression, Condition condition) where T : class
{
  return PexProtector.Invoke<MethodCallReturn<T, TProperty>>((Func<MethodCallReturn<T, TProperty>>) (() =>
  {
    if (ExpressionExtensions.IsPropertyIndexer((LambdaExpression) expression))
      return Mock.Setup<T, TProperty>(mock, expression, condition);
    ...
  }
  ...
}

To answer your question, here is a code sample using underlying Dictionary to store values:

var dictionary = new Dictionary<string, object>();

var applicationSettingsBaseMock = new Mock<SettingsBase>();
applicationSettingsBaseMock
	.Setup(sb => sb[It.IsAny<string>()])
	.Returns((string key) => dictionary[key]);
applicationSettingsBaseMock
	.SetupSet(sb => sb["Expected Key"] = It.IsAny<object>())
	.Callback((string key, object value) => dictionary[key] = value);

As you can see, you have to explicitly specify key to set up indexer setter. Details are described in another SO question: https://stackoverflow.com/questions/29697406/moq-an-indexed-property-and-use-the-index-value-in-the-return-callback

Solution 4 - C#

Its not that difficult but it took a little bit to find it :)

var request = new Moq.Mock<HttpRequestBase>();
request.SetupGet(r => r["foo"]).Returns("bar");

Solution 5 - C#

It appears that what I was attempting to do with MOQ is not possible.

Essentially I was attempting to MOQ a HTTPSession type object, where the key of the item being set to the index could only be determined at runtime. Access to the indexed property needed to return the value which was previously set. This works for integer based indexes, but string based indexes do not work.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAshView Question on Stackoverflow
Solution 1 - C#Mike ScottView Answer on Stackoverflow
Solution 2 - C#waskerView Answer on Stackoverflow
Solution 3 - C#Vitaliy UlantikovView Answer on Stackoverflow
Solution 4 - C#JustEnglandView Answer on Stackoverflow
Solution 5 - C#AshView Answer on Stackoverflow