Search code examples
c#.netnunitassertionsfluent-interface

Assert that value is equal to any of a collection of expected values


Does NUnit provide a constraint to find whether the actual value is the element of a given enumerable or array, in other words, that it is equal to any of multiple expected values? Something like:

Assert.That(actual, Is.EqualToAnyOf(new[] { 1, 2, 3 }))

That is, to point out, the actual is a single value. I expect the value to be either 1, 2, or 3. The assertion

Assert.That(actual, Contains.Element(expected))

checks logically the same, but it is the opposite intention: Here we have a collection of actual values and expect one value to be in it.

Furthermore, I found these but they all don't fit:

Assert.That(actual, Is.EqualTo(expected)) // only allows one value
Assert.That(actual, Is.InRange(start, end)) // only works for consecutive numbers
Assert.That(actual, Is.SubsetOf(expected)) // only works if actual is an enumerable
Assert.That(expected.Contains(actual)) // meaningless "expected: true but was: false" message

Solution

  • The only way I could see to accomplish this is by creating your own constraint. It's pretty straightforward to do though.

    The constraint class itself:

    public class OneOfValuesConstraint : EqualConstraint
    {
        readonly ICollection expected;
        NUnitEqualityComparer comparer = new NUnitEqualityComparer();
    
        public OneOfValuesConstraint(ICollection expected)
            : base(expected)
        {
            this.expected = expected;
        }
    
        public override bool Matches(object actual)
        {
            // set the base class value so it appears in the error message
            this.actual = actual;
            Tolerance tolerance = Tolerance.Empty;
    
            // Loop through the expected values and return true on first match
            foreach (object value in expected)
                if (comparer.AreEqual(value, actual, ref tolerance))
                    return true;
    
            // No matches, return false
            return false;
        }
    
        // Overridden for a cleaner error message (contributed by @chiccodoro)
        public override void WriteMessageTo(MessageWriter writer)
        {
            writer.DisplayDifferences(this);
        }
    
        public override void WriteDescriptionTo(MessageWriter writer)
        {
            writer.Write("either of ");
            writer.WriteExpectedValue(this.expected);
        }
    }
    

    And to make it fluent, create a static method to wrap it (contributed by @chicodorro):

    public static class IsEqual
    {
        public static OneOfValuesConstraint ToAny(ICollection expected)
        {
            return new OneOfValuesConstraint(expected);
        }
    }    
    

    Then to use it:

    int[] expectedValues = new[] { 0, 1, 2 };
    Assert.That(6, IsEqual.ToAny(expectedValues));
    

    Fails with the message:

    Expected: either of < 0, 1, 2 >

    But was: 6