FakeItEasy期望对HashSet比较失败

时间:2014-08-10 00:41:24

标签: fakeiteasy

我在Mac OS X 10.9.4上使用Xamarin Studio 5.2,NUnit 2.6.3和FakeItEasy 1.23.0。

当我为此代码运行测试时:

using System;
using ValueSet = System.Collections.Generic.HashSet<uint>;
using NUnit.Framework;
using FakeItEasy;

namespace SetTest
{
    [TestFixture]
    class TestFixture
    {
        [Test]
        public void CallsUsersWithSetAndReducedSet()
        {
            var values = new ValueSet { 1, 2, 3 };

            var setUser = A.Fake<SetUser>();

            ClassUnderTest testInstance = new ClassUnderTest();

            using (var scope = Fake.CreateScope())
            {
                testInstance.RunWith(setUser);

                using (scope.OrderedAssertions())
                {
                    A.CallTo(() => setUser.Use(A<ValueSet>.That.IsEqualTo(values))).MustHaveHappened(Repeated.Exactly.Once);
                    A.CallTo(() => setUser.Use(A<ValueSet>.That.Matches(set =>
                        set.Count == 2 && set.Contains(1)))).MustHaveHappened(Repeated.Exactly.Once);
                }
            }
        }
    }

    public class SetUser
    {
        public virtual void Use(ValueSet set)
        {
        }
    }

    class ClassUnderTest
    {
        public static void Main(string[] arguments)
        {
        }

        public void RunWith(SetUser setUser)
        {
            var values = new ValueSet { 1, 2, 3 };
            setUser.Use(values);

            values.Remove(3);
            setUser.Use(values);
        }
    }
}

我收到以下错误输出:

FakeItEasy.ExpectationException:以下调用的断言失败:SetTest.SetUser.Use(1 [System.UInt32]&gt;)预计只找到一次,但在调用中找到#0次:   1. SetTest.SetUser.Use(设置:System.Collection.Generic.HashSet1 [System.UInt32])重复2次

我不明白造成这种失败的原因以及解决方法。 让这种类型的测试通过需要什么?

1 个答案:

答案 0 :(得分:2)

@Tim Long在评论中走在正确的轨道上 这里有更多细节,以及回复您对2014-08-11 03:25:56的评论的更新:

第一个MustHaveHappened失败的第一个原因:

根据FakeItEasy argument constraints documentationThat.IsEqualTo测试“使用object.Equals的对象相等”。这就是造成意外行为的原因。

未将values传递给方法不一定是个问题,或者如果ValueSet.Equals执行了值比较,则不会,ValueSetHashSet<uint>,因此,您可以看到from that class's method documentation它没有使用object.Equalstests for reference equality。因此,您的IsEqualTo断言失败。如果您使用更复杂的匹配器对HashSet执行值类型比较,可能更接近您在第二个A.CallTo中使用的内容,或者使用That.Contains的内容,我认为您会有更好的成功。

您可能会考虑使用That.IsSameSequenceAs,但是如果这样做则要小心:HashSet不保证枚举中元素的顺序,所以即使集合具有相同的元素,你可能会失败。

第一个MustHaveHappened失败的第二个原因:

RunWith更改values之间调用的内容setUser.Use。因此,在两个调用中使用相同的集合,首先使用3个元素,然后在它只有2个元素时使用。这意味着,在进行第一次MustHaveHappened调用时,该集合只有2个元素,因此比较失败。你可以通过writing an argument formatter for the ValueSet更清楚地看到这一点。这将提供更多信息。

不匹配的原因是当对伪造方法进行调用时,FakeItEasy会捕获参数。但是,对于引用类型,例如ValueSetHashSet),仅保留对参数的引用。因此,如果稍后修改对象,特别是在测试的执行和验证阶段之间,则该对象看起来与伪造调用时的对象不同。请参阅@ jimmy_keen对MustHaveHappened fails when called twice on the same object的回答。在FakeItEasy Issue 306 - Verifying multiple method calls with reference parameters处进行了一些讨论。

在这种情况下,通常的方法是按照他的建议做 - 提供代码以在调用时捕获传入参数的重要状态,然后稍后查询保存状态。

您可能可以使用以下内容:

[Test]
public void CallsUsersWithSetAndReducedSet()
{
    var capturedValueSets = new List<List<uint>>();

    var setUser = A.Fake<SetUser>();
    A.CallTo(() => setUser.Use(A<ValueSet>._)) // matches any call to setUser.Use
        .Invokes((ValueSet theSet) => capturedValueSets.Add(theSet.ToList()));

    ClassUnderTest testInstance = new ClassUnderTest();

    testInstance.RunWith(setUser);

    Assert.That(capturedValueSets, Has.Count.EqualTo(2),
        "not enough calls to setUser.Use");
    Assert.That(capturedValueSets[0], Is.EquivalentTo(new uint[] {1, 2, 3}),
        "bad set passed to first call to setUser.Use");
    Assert.That(capturedValueSets[1], Has.Count.EqualTo(2) & Has.Member(1),
        "bad set passed to second call to setUser.Use");
}

您可以看到,每次调用Use时,我们都会将ValueSet参数的内容添加到capturedValueSets。然后在最后我们

  1. 通过检查capturedValueSets
  2. 的长度,确保进行了两次通话
  3. 确保第一次调用Use时,该集合包含元素1,2和3. Is.EquivalentTo检查两个列表但忽略顺序
  4. 确保第二次调用Use,该集合有2个元素,其中一个是1
  5. 通过依次检查两个捕获的值集,有关范围和有序断言的所有位变得不必要。