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

4

7

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
Gilchrist answered 27/8, 2014 at 8:20 Comment(0)
A
2

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

Airspeed answered 27/8, 2014 at 13:0 Comment(3)
+1 No idea why your answer was downvoted. I guess it is closest to what I wanted. I was just hoping this assertion would be provided out of the box.Gilchrist
I have solved the "homework" (1) by using: writer.WritePredicate("either of"); writer.WriteExpectedValue(this.expected); and (2) by providing a IsEqual.ToAny(expected) method. However in order for the message to apply I had to change the base class to Constraint. Don't ask me why... The message now reads: Expected: either of < 0, 1, 2 > But was: 6Gilchrist
Thanks for the contributions, @chiccodoro, the answer is updated!Airspeed
S
5

CollectionAssert should be what you need if I am not overlooking something. It is as simple as:

CollectionAssert.Contains(IEnumerable expected, object actual);

However, there seems to be several ways to achieve your goal, such as:

[Test]
public void CollectionContains()
{
    var expected = new List<int> { 0, 1, 2, 3, 5 };
    var actual = 5;

    CollectionAssert.Contains(expected, actual);
    Assert.That(expected, Contains.Item(actual));
}

Above assertions should basically assert the same and could be used interchangeably.

Edit: Question was modified, stating that Assert.That(expected, Contains.Item(actual)); is not valid even though it logically tests the same thing.

Snowdrift answered 27/8, 2014 at 21:30 Comment(10)
unfortunately you changed the title of my question such that this would be an answer but it is not. Your proposal mixes up expected and actual just as some of my examples did. Look at the error message that appears if the assertion fails.Gilchrist
funnily the resource you referenced documents the signature as you wrote: IEnumerable expected, object actual. But this is wrong. The assertion that this method provides is the opposite. If you make actual = 6 in your example, this is what you get: Expected: collection containing 6 But was: < 0, 1, 2, 3, 5 >Gilchrist
Well, this is really only about wording... You can rename expected to collection and assertion failed message would make perfect sense, i.e. Expected: collection containing 6 But was: < 0, 1, 2, 3, 5 >. I can't believe that you will rather write your own assertion if you can even use different override of CollectionAssert.Contains where you will specify assertion failed message explicitly. FYI this is the approach we are using in corporation with 24K employees and tens of pages of coding standards and this approach is considered OK.Snowdrift
On top of that, "referenced resource" is official NUnit documentation, I can't imagine more valid source of information in this case. Regarding edit of your question - I have too low reputation to perform edits on my own, it had to be peer reviewed so it obviously made sense to even somebody else thatn just to me.Snowdrift
I think I was not clear enough. I don't have an "actual" collection and expect a certain value in it. I have an actual value and expect that it equals either of several values. The assertion message works well if the collection is the actual thing. As for the peer review, I have reviewed a lot of edits. It is not always possible to detect such nuances. I did not intend to offend you. It is just a fact that my intention was lost by the change of the title.Gilchrist
as for "this is really only about wording" - to me the wording of the assertion messages is important. And it seems to be important to other people too, otherwise the only assertion required would be Assert.IsTrue(...). Of course the wording does not influence whether a test goes red or green. But if it is red it can help to quickly capture what is going wrong. A vague message such as "expected: true but was: false" helps less. But a wrong message is misleading and worse than a vague one.Gilchrist
As I said, there are overrides which let you set the assertion message explicitly if that is your main concern. One more note, I believe these two statements are equivalent "collection contains an element" == "element is contained within collection". Therefore I still consider at least a solution Assert.That(expected, Contains.Item(actual)); to be valid. Even error message makes perfect sense to me, i.e. Expected: collection containing 6 But was: < 0, 1, 2, 3, 5 >. If you don't like it then I am afraid your question should be edited a bit more as it seems to me this is valid answer.Snowdrift
I have edited my question one more time. I tried my best. I hope that in combination with the already accepted answer the intention will be clear to the future readers. děkuji, anyway.Gilchrist
Let us continue this discussion in chat.Snowdrift
sorry but I don't have access to chat, and my question is already answered.Gilchrist
M
3

There is a way to do this built in to NUnit, using the Or constraint:

Assert.That(actual, Is.EqualTo(1).Or.EqualTo(2).Or.EqualTo(3))

If your list is more dynamic, you can build your list of Ors like this:

var expected = new[] { 1, 2, 3 };
var constraints = Is.EqualTo(expected[0]);

for(var i = 1; i < expected.Length; i++)
    constraints = constraints.Or.EqualTo(expected[i]);

Assert.That(actual, constraints);

That latter answer doesn't read as well in the fluid syntax, but does achieve the dynamic building of or constraints. You could probably wrap that in a custom constraint as patrick-quirk demonstrated in order to achieve a more readbale fluid syntax, but that's up to you.

Machutte answered 24/2, 2017 at 13:13 Comment(0)
O
3

I know this is an old question, bu I have a maybe better (and native) suggestion. With NUnit 3.x (I'm on 3.10.1) you can use Is.AnyOf:

 Assert.That(
actualValue, 
Is.AnyOf(expectedValue1, expectedValue2, expectedValue3),
"My error message");
Outgeneral answered 30/11, 2018 at 9:58 Comment(1)
If the question would be posed now, this should be the accepted answer!Arginine
A
2

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

Airspeed answered 27/8, 2014 at 13:0 Comment(3)
+1 No idea why your answer was downvoted. I guess it is closest to what I wanted. I was just hoping this assertion would be provided out of the box.Gilchrist
I have solved the "homework" (1) by using: writer.WritePredicate("either of"); writer.WriteExpectedValue(this.expected); and (2) by providing a IsEqual.ToAny(expected) method. However in order for the message to apply I had to change the base class to Constraint. Don't ask me why... The message now reads: Expected: either of < 0, 1, 2 > But was: 6Gilchrist
Thanks for the contributions, @chiccodoro, the answer is updated!Airspeed

© 2022 - 2024 — McMap. All rights reserved.