验证对字典中索引属性的调用

时间:2018-02-19 23:09:42

标签: c# dictionary dependency-injection mocking moq

我正在尝试模拟包含字典的类,并验证对字典的特定索引的调用。

该类的接口如下所示:

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框架可以验证字典本身的获取和设置,但不是特定的键或索引。我的问题是;什么是在模拟类中验证字典中特定键的获取和设置的正确方法?

2 个答案:

答案 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(和任何内部)。