我正在尝试模拟包含字典的类,并验证对字典的特定索引的调用。
该类的接口如下所示:
public interface IClassWithADictionary
{
IDictionary<string, string> Dictionary { get; }
}
课程看起来像:
public class ConcreteClassWithADictionary : IClassWithADictionary
{
public ConcreteClassWithADictionary(IDictionary<string, string> dictionary)
{
Dictionary = dictionary;
}
public IDictionary<string, string> Dictionary { get; }
}
现在这里我不明白,我有一个测试用例,我试图验证字典中的特定键已被设置,后来我试图验证已检索到此密钥。
[Test]
public void MyTest()
{
var concreteClassWithADictionaryMock = new Mock<IClassWithADictionary>();
var dictionary = new Dictionary<string, string>();
concreteClassWithADictionaryMock
.Setup(m => m.Dictionary)
.Returns(dictionary); // Setting up mock to return a concrete dictionary
var key = "Key";
var value = "Value";
concreteClassWithADictionaryMock.Object.Dictionary[key] = value; // Setting the value
var test = concreteClassWithADictionaryMock.Object.Dictionary[key]; // Getting the value
// Passes here - no index specified
concreteClassWithADictionaryMock.Verify(m => m.Dictionary);
// Passes here - with VerifyGet() too
concreteClassWithADictionaryMock.VerifyGet(m => m.Dictionary);
// Fails here - throws exception, "Expression is not a property access: m => m.Dictionary[.key]"
concreteClassWithADictionaryMock.VerifyGet(m => m.Dictionary[key]);
//Fails here - no invocation performed, doesn't seem to like the set or the key indexer
concreteClassWithADictionaryMock.VerifySet(m => m.Dictionary[key] = value);
// Fails here - no invocation performed, even with verifying index access of some kind
concreteClassWithADictionaryMock.Verify(m => m.Dictionary[key]);
}
显然,Moq框架可以验证字典本身的获取和设置,但不是特定的键或索引。我的问题是;什么是在模拟类中验证字典中特定键的获取和设置的正确方法?
答案 0 :(得分:0)
答案有点微不足道。
你应该模拟mock类返回的字典,然后在其上调用verify方法。
答案 1 :(得分:0)
有趣的是我之前没有注意到,但那是深夜。基本上,它是Cameron所说的:你应该嘲笑IDictionary。这就是原因:
var dictionary = new Dictionary<string, string>(); // <---- HEEERE
concreteClassWithADictionaryMock
.Setup(m => m.Dictionary)
.Returns(dictionary); // Setting up mock to return a concrete dictionary
// ...
concreteClassWithADictionaryMock.Verify(m => m.Dictionary); // A
concreteClassWithADictionaryMock.VerifyGet(m => m.Dictionary[key]); // B
Mock不会在他们之间分享信息 此外,Mocks不是病毒。他们不会感染其他课程以注入额外的功能 此外,Mocks不是剖析器。他们不会改变运行时以注入额外的功能。
这使得Mocks不是透视的:他们只看到对他们做了什么,并且不知道发生了什么在他们周围。
现在我们可以说明一点:要验证工作,必须记录方法调用。方法调用发生在对象上。接到电话的对象知道它。运行时知道它。潜在的探查者知道它。而且没有其他人。
首先,我们在这里有什么电话和对象?
mConcrete.Dictionary[key] :
1) mock get_Dictionary (property getter)
2) dict get_Item (aka. this[])
接下来,进行录音的是什么?当然没有正常的典型课程。它是所有进行录音的Mocks。
mConcrete.Dictionary[key] :
1) mock get_Dictionary (property getter) <-- 'm' records the call
and returns dict
according to setup
然后
mConcrete.Dictionary[key] :
1) ...
2) dict get_Item (aka. this[]) <-- plain Dict returns item
no recording happens
现在,当您执行验证时,验证可以验证与&#39; m&#39;相关的任何内容,但是当您开始向下钻取&#39;进入m.Dict[]
它会撞到一堵墙,因为字典很笨,没有记录任何东西,更糟糕的是 - 它不是模拟,所以它甚至不会响应预期的录音相关界面。
如何解决这个问题?进行适当的设置并尽可能多地模拟 。模仿IDictionary而不是使用普通的。
var mDictionary = new Mock<IDictionary<string, string>>();
mDictionary.Setup(d => d["key"]).Returns("value");
var mConcrete = new Mock<IClassWithADictionary>();
mConcrete
.Setup(m => m.Dictionary)
.Returns(mDictionary.Object); // Setting up mock to return a mock
var test = mConcrete.Object.Dictionary[key]; // Getting the value
// verify if the Dictionary-Itself was read
mConcrete.Verify(m => m.Dictionary);
// verify if the Dictionary-Contents was read
mDictionary.Verify(m => m.Dictionary["key"]);
请注意,在最后一个中,我正在验证字典模拟。这是因为mConcrete 仍然不知道除了它自己以外的任何其他对象上发生了什么。
最后一点感兴趣:Moq实际上支持创建&#34;模拟对象图&#34;在飞行中。来自Moq Quickstart:
// Make an automatic recursive mock: a mock that will return a new mock for every member that doesn't have an expectation and whose return value can be mocked (i.e. it is not a value type)
// default DefaultValue is DefaultValue.Empty, i.e. NULL of ref types
// let's change that
var mock = new Mock<IFoo> { DefaultValue = DefaultValue.Mock };
// this property access would normally return a NULL
// but now returns a mock of Bar
Bar value = mock.Object.Bar;
// the returned mock is reused, so further accesses to the property return
// the same mock instance. this allows us to also use this instance to
// set further expectations on it if we want
var barMock = Mock.Get(value);
barMock.Setup(b => b.Submit()).Returns(true);
如果您已经嵌套了&#39;接口,就像这里 - 返回IDictionary的IConcreteClass(它确实返回IDictionary而不是Dictionary,对吗?:))然后你可以使用这个功能让Moq自动创建IDictionary mock(和任何内部)。