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
chiccodoro
  • 14,407
  • 19
  • 87
  • 130

4 Answers4

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.

Michal Hosala
  • 5,570
  • 1
  • 22
  • 49
  • 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. – chiccodoro Aug 28 '14 at 07:12
  • 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 >` – chiccodoro Aug 28 '14 at 07:22
  • 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. – Michal Hosala Aug 28 '14 at 07:51
  • 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. – Michal Hosala Aug 28 '14 at 07:52
  • 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. – chiccodoro Aug 28 '14 at 07:58
  • 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. – chiccodoro Aug 28 '14 at 07:59
  • 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. – Michal Hosala Aug 28 '14 at 08:12
  • 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. – chiccodoro Aug 28 '14 at 09:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60158/discussion-between-michal-hosala-and-chiccodoro). – Michal Hosala Aug 28 '14 at 09:59
  • sorry but I don't have access to chat, and my question is already answered. – chiccodoro Aug 28 '14 at 11:27
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.

cidthecoatrack
  • 1,441
  • 2
  • 18
  • 32
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");
NinjaCross
  • 784
  • 2
  • 9
  • 20
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

Community
  • 1
  • 1
Patrick Quirk
  • 23,334
  • 2
  • 57
  • 88
  • +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. – chiccodoro Aug 28 '14 at 07:16
  • 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: 6` – chiccodoro Aug 28 '14 at 07:50
  • Thanks for the contributions, @chiccodoro, the answer is updated! – Patrick Quirk Aug 28 '14 at 12:46