1

I'm moqing an interface which has:

Dictionary<string, object> InstanceVariables { get; set; }

I've created a new mock of the interface and trying to set it up so it returns just a random string like so:

_mockContext.SetupGet(m => m.InstanceVariables[It.IsAny<string>()]).Returns(@"c:\users\randomplace");

But I seem to get an error:

{"Invalid setup on a non-virtual (overridable in VB) member: m => m.InstanceVariables[It.IsAny<String>()]"}

What does this mean exactly? I am mocking the interface so shouldn't this not be a problem?

Thanks

magna_nz
  • 1,243
  • 5
  • 23
  • 42
  • Why do you need to mock a `Dictionary` if you can create a dictionary will all required fields? – Valentin Jun 12 '16 at 21:21
  • Because I want to change what comes out of InstanceVariables when I execute the method that I've passed in my mockContext object into – magna_nz Jun 12 '16 at 21:23
  • 3
    Your property is a `Dictionary`, not an `IDictionary`. Therefore, to make it compile you must be mocking the concrete implementation, *not* the interface as you state (although I can't know that for sure, as you didn't post that code). Therefore, the error is correct - you can't override a non-virtual member (and the dictionary indexer is not virtual). However, @Valentin's question still stands - why not just create a dictionary with the values set as you desire for your unit test - a dictionary is a dumb data-store - there is no functionality there to mock! – RB. Jun 12 '16 at 21:51

1 Answers1

7

There's a number of reasons I'd advise against it. Firstly, as mentioned in the comments there's no reason why a real dictionary wouldn't work here. We shouldn't mock types that what we don't own and only mock types we do own.
The line _mockContext.SetupGet(m => m.InstanceVariables[It.IsAny<string>()]).Returns(@"c:\users\randomplace"); is trying to mock the Get on Dictionary<T, T> which as @RB notes isn't virtual, hence your error. You may be mocking your interface, but the set up there is on the .NET dictionary.

Secondly, IMO, It.IsAny<string>() leads to quite weak tests, because it will respond to, well, any string. Structuring something like:

const string MyKey = "someKey";

var dictionary =  new Dictionary<string, object>();
dictionary.Add(MyKey, @"c:\users\randomplace");
_mockContext.Setup(m => m.InstanceVariables).Returns(dictionary);

var sut = new SomeObject(_mockContext.Object());
var result = sut.Act(MyKey);

// Verify

will be stronger as the dictionary can only respond with the path if the correct key is given to / or generated by your System Under Test (sut).


That said, if you absolutely must mock a dictionary, for reasons that aren't apparent on the question... then the property on your interface needs to be the interface for the dictionary, IDictionary, not the concrete class:

IDictionary<string, object> InstanceVariables { get; set; }

Then you can create the dictionary mock via:

var dictionary = new Mock<IDictionary<string, object>>();
dictionary.SetupGet(d => d[It.IsAny<string>()]).Returns(@"c:\users\randomplace");

Then on the context mock:

_mockContext.SetupGet(d => d.InstanceVariables).Returns(dictionary.Object);

Why does it need to be Virtual?

Moq and other similar mocking frameworks can only mock interfaces, abstract methods/properties (on abstract classes) or virtual methods/properties on concrete classes.

This is because it generates a proxy that will implement the interface or create a derived class that overrides those overrideable methods in order to intercept calls.
Credit to @aqwert

Community
  • 1
  • 1
NikolaiDante
  • 18,469
  • 14
  • 77
  • 117
  • Good answer. To see that the indexer of the concrete class `Dictionary<,>` is indeed non-virtual, see [its documentation](https://msdn.microsoft.com/en-us/library/9tee9ht2.aspx). To see that the interface `IDictionary<,>` includes the member (the indexer), see [its page](https://msdn.microsoft.com/en-us/library/zyxt2e2h.aspx). I absolutely agree that it is probably best to create a standard populated `Dictionary<,>` in the test, and return that from the mock. However, in the second approach, you do not have to setup `It.IsAny()`. You can setup only `d => d["someKey"]` if you want. – Jeppe Stig Nielsen Jun 13 '16 at 09:00