Search code examples
c#unit-testingrhino-mocks

Why get "Expected #0, Actual #1" when using StringDictionary in RhinoMocks 3.6


Today I changed to use StringDictionary instead of Dictionary<string,string> in my code, but old unit test failed. So I write a small unit test to test this.

Here is my small test:

using Rhino.Mocks;
using NUnit.Framework;
//using CustomDictionary = System.Collections.Specialized.StringDictionary;
using CustomDictionary = System.Collections.Generic.Dictionary<string, string>;

namespace ConsoleApplication1
{
    public interface ITest
    {
        void DoSth(CustomDictionary dic);
    }
    public class OneTest : ITest
    {
        public void DoSth(CustomDictionary dic) {/*do nothing*/}
    }

    [TestFixture]
    public class TestClass
    {
        [Test]
        public void Test1()
        {
            var mockRepository = new MockRepository();
            var test = mockRepository.StrictMock<ITest>();

            using (mockRepository.Record())
            {
                Expect.Call(() => test.DoSth(new CustomDictionary { { "Test", "Test1" } }));
            }
            test.DoSth(new CustomDictionary { { "Test", "Test1" } });

            mockRepository.VerifyAll();
        }
    }
}

If I use Dictionary<string,string>, the test will pass, but when I use StringDictionary, the test failed.

What's the problem here?


Solution

  • Problem goes from StringDictionary instances comparison. There is no overridden Equals method, thus instances compared not by content, but by references. If you will use same instance, test will pass:

        [Test]
        public void Test1()
        {
            var mockRepository = new MockRepository();
            var test = mockRepository.StrictMock<ITest>();
            var dictionary = new CustomDictionary { { "Test", "Test1" } };
    
            using (mockRepository.Record())            
                Expect.Call(() => test.DoSth(dictionary));
    
            test.DoSth(dictionary);
            mockRepository.VerifyAll();
        }
    

    You can override Equals on your CustomDictionary class to make your original test pass:

    public override bool Equals(object obj)
    {
        CustomDictionary other = obj as CustomDictionary;
        if (other == null)
            return false;
    
        if (Count != other.Count)
            return false;
    
        foreach (string key in Keys)
        {
            if (!other.ContainsKey(key))
                return false;
    
            if (this[key] != other[key])
                return false;
        }
    
        foreach (string key in other.Keys)
        {
            if (!ContainsKey(key))
                return false;
        }
    
        return true;
    }
    

    BTW I hope this is not your real test, because here you are testing mock, instead of testing your code.

    WHY YOUR CODE WORKS WITH DICTIONARY:

    As I understand RhinoMocks implementation, Rhino.Mocks.Impl.Validate class used for arguments validation. You can take a look on it's ArgsEqual method implementation:

    public static bool ArgsEqual(object[] expectedArgs, object[] actualArgs)
    {
        return RecursiveCollectionEqual(expectedArgs, actualArgs);
    }
    

    I leave to you details of RecursiveCollectionEqual, but interesting part there is arguments comparison:

    if (current == null)
    {
        if (actual != null)
            return false;
    }
    else if (!SafeEquals(current, actual))
    {
        if (current is ICollection)
        {
            if (!RecursiveCollectionEqual((ICollection)current, (ICollection)actual))
                return false;
    
            continue;
        }
        return false;
    }
    

    As you can see, if argument is ICollection then Rhino goes deeper to compare expected and actual collections. Dictionary implements ICollection, but StringDictionary does not. Thus arguments of StringDictionary types compared only by reference.

    UPDATE: Didn't notice, that you have an alias. Just inherit from type instead, and you will be able to override Equals:

    public class CustomDictionary : StringDictionary