Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I mock a collection using Moq

I'm brand new to unit testing and mocking and still wet behind the ears. I'm using the Moq framework and I need to mock a collection such that it yields a single member with a value I supply.

The collection class in question is a System.Configuration.SettingsPropertyCollection, which contains SettingsProperty objects.

In turn, the SettingsProperty has an Attributes property that returns a SettingsAttributeDictionary.

I need my collection to yield a single SettingsProperty, which has a single custom attribute (derived from System.Attribute) in its Attributes.SettingsAttributeDictionary.

I'm really struggling to get my head around this, but so far to no avail. I've tried sticking out my tongue and pulling funny faces at it, but nothing works.

Here's the code I've tried so far, an exception is thrown at the point commented in the code and so of course the test always fails.

    [TestMethod]
    public void GetPropertySettings_Should_Return_Default_Values_When_Device_Not_Registered()
        {
        const string deviceName = "My.UnitTest";
        const string deviceType = "Switch";
        var deviceId = String.Format("{0}.{1}", deviceName, deviceType);
        Mock<IProfile> mockProfile = new Mock<IProfile>();
        Mock<SettingsContext> mockSettingsContext = new Mock<SettingsContext>();
        // Construct a SettingsPropertyCollection populated with a single property.
        // The single property will have a fixed name and default value, and will also have a single
        // attribute, giving teh ASCOM DeviceId.
        var deviceAttribute = new ASCOM.DeviceIdAttribute(deviceId);
        var attributes = new SettingsAttributeDictionary();
        attributes.Add(typeof(DeviceIdAttribute), deviceAttribute);
        var settingsProperty = new SettingsProperty(SettingName, typeof(string), null, false, SettingDefaultValue, SettingsSerializeAs.String, attributes, true, true);
        var propertyCollection = new SettingsPropertyCollection();
        propertyCollection.Add(settingsProperty);

        // Now comes the interesting part where we call our IProfile - this is where we really need Moq.
        // Expectations:
        //  - mockProfile must have it's DeviceType set.
        //  - mockProfile's device type (captured in setDeviceType) must match deviceType.
        //  - The returned SettingsPropertyValueCollection must not be empty.
        //  - The returned SettingsPropertyValueCollection must have exactly one entry.
        //  - The entry must match the value of SettingDefaultValue. 

        // Expectation: IProfile must have its DeviceType set.  We capture the value into setDeviceType.
        var setDeviceType = String.Empty;
        mockProfile.SetupSet(x => x.DeviceType).Callback(y => setDeviceType = y);

        // Finally, it is time to call the method we want to test
        var settingsProvider = new SettingsProvider(mockProfile.Object);

        // THE NEXT LINE THROWS AN EXCEPTION
        // IF I TRY TO STEP INTO IT, IT NEVER RETURNS AND THE TEST RUN JUST ENDS.
        var result = settingsProvider.GetPropertyValues(mockSettingsContext.Object, propertyCollection);

        // Now lets verify that everything went as expected
        // First, let's test that the parsing of DeviceId was as expected: IProvider.DeviceType was set to the expected value
        Assert.AreEqual(deviceType, setDeviceType);
        // Then let's test that the methods of IProvider that we mocked were called
        mockProfile.VerifyAll();

        // With this done, let's turn to the output of the method
        // Firstly, we test that the resulting collection contains exactly one item of the type SettingsPropertyValue
        Assert.IsTrue(result.Count > 0);
        Assert.AreEqual(1, result.Count);
        Assert.IsTrue(result.OfType<SettingsPropertyValue>().Count() > 0);

        // Then let's inspect the contained SettingsProviderValue further
        var settingsPropertyValue = result.OfType<SettingsPropertyValue>().First();

        // First IsDirty flag must never be set
        Assert.IsFalse(settingsPropertyValue.IsDirty);

        // The PropertyValue must be the default value we passed in
        Assert.AreEqual(SettingDefaultValue, settingsPropertyValue.PropertyValue);
        }

The exception that gets thrown (as reported by the test runner) is:

Test method ASCOM.Platform.Test.SettingsProviderTest.GetPropertySettings_Should_Return_Default_Values_When_Device_Not_Registered threw exception: System.ArgumentException: The type System.Configuration.SettingsContext implements ISerializable, but failed to provide a deserialization constructor.

like image 910
Tim Long Avatar asked Feb 05 '10 21:02

Tim Long


People also ask

How do you mock a method in Moq?

First, we instantiate the FakeDbArticleMock class and indicate which setup we want to use for this test. Then, it is necessary to instantiate the repository we want to test and inject the mock instance into it. Finally, we call the method we are testing and assert the results.

What can be mocked with Moq?

You can use Moq to create mock objects that simulate or mimic a real object. Moq can be used to mock both classes and interfaces.

What is Moq mocking framework?

Moq is a mocking framework built to facilitate the testing of components with dependencies. As shown earlier, dealing with dependencies could be cumbersome because it requires the creation of test doubles like fakes. Moq makes the creation of fakes redundant by using dynamically generated types.


1 Answers

I believe you need to set up the Attributes property of your mock SettingsProperty to return your mock SettingsAttributeDictionary and then set up the indexer of your mock SettingsAttributeDictionary to return your desired value, e.g.

mockItem.SetupGet(x => x.Attributes).Returns(mockAttributes);

mockAttributes.SetupGet(x => x[It.IsAny<System.Type>()])
              .Returns(deviceAttribute);

Edit

The exception is being thrown in the Castle DynamicProxy class which is used by Moq. I believe this is to do with how Moq mocks objects which are serializable. If castle can't find a non-public constructor on a serializable object with a signature of (SerializationInfo, StreamingContext) then it throws this exception. What you could do, is change the GetPropertyValues method of your custom SettingsProvider so that it accepts a Hashtable instead of a SettingsContext, and provide a mock Hashtable to the method call instead of a mock SettingsContext. Hashtable has the required constructor so perhaps this will work. There is no real advantage to insisting on a parameter of type SettingsContext rather than Hashtable since SettingsContext is only trivially derives from Hashtable, i.e. it adds no members.

like image 173
Adam Ralph Avatar answered Oct 01 '22 17:10

Adam Ralph