magna_nz
magna_nz

Reputation: 1273

Invalid setup on a non virtual member using MOQ

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

Upvotes: 1

Views: 18789

Answers (1)

NikolaiDante
NikolaiDante

Reputation: 18649

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

Upvotes: 7

Related Questions