Skip to content

Chaining multiple collection asserts with index #4423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
azygis opened this issue Jul 19, 2023 · 3 comments · Fixed by #4480
Closed

Chaining multiple collection asserts with index #4423

azygis opened this issue Jul 19, 2023 · 3 comments · Fixed by #4480
Labels
Milestone

Comments

@azygis
Copy link

azygis commented Jul 19, 2023

I'm not sure whether it's a bug or not, but consider the following code:

[Test]
public void Dummy()
{
    var items = new SomeStuff[]
    {
        new("Name 1"),
        new("Name 2")
    };

    // OK
    Assert.That(items, Has.Exactly(2).Items);
    Assert.That(items, Has.ItemAt(0).Property(nameof(SomeStuff.Name)).EqualTo("Name 1"));
    Assert.That(items, Has.ItemAt(1).Property(nameof(SomeStuff.Name)).EqualTo("Name 2"));

    // OK
    Assert.That(items, Has.Exactly(2).Items
        .And.One.Property(nameof(SomeStuff.Name)).EqualTo("Name 1")
        .And.One.Property(nameof(SomeStuff.Name)).EqualTo("Name 2"));

    // NOT OK 1
    Assert.That(items, Has.Exactly(2).Items
        .And.ItemAt(0).Property(nameof(SomeStuff.Name)).EqualTo("Name 1")
        .And.ItemAt(1).Property(nameof(SomeStuff.Name)).EqualTo("Name 2"));

    // NOT OK 2
    Assert.That(items, Has.Exactly(2).Items
        .And.One.ItemAt(0).Property(nameof(SomeStuff.Name)).EqualTo("Name 1")
        .And.One.ItemAt(1).Property(nameof(SomeStuff.Name)).EqualTo("Name 2"));
}

public sealed record SomeStuff(string Name);

How would one chain AndConstraint and use the indexer in the same Assert.That call? The NOT OK 1 assert throws:

System.InvalidOperationException : Stack empty.
   at System.Collections.Generic.Stack`1.ThrowForEmptyStack()
   at System.Collections.Generic.Stack`1.Pop()
   at NUnit.Framework.Constraints.ConstraintBuilder.ConstraintStack.Pop()
   at NUnit.Framework.Constraints.BinaryOperator.Reduce(ConstraintStack stack)
   at NUnit.Framework.Constraints.ConstraintBuilder.Resolve()
   at NUnit.Framework.Constraints.Constraint.NUnit.Framework.Constraints.IResolveConstraint.Resolve()
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression, String message, Object[] args)
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression)
   at SomeTests.Dummy() in /path/TestFile.cs:line 115
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

The NOT OK 2 assert throws:

System.ArgumentException : Default indexer accepting arguments < 0 > was not found on SomeTests+SomeStuff.
   at NUnit.Framework.Constraints.IndexerConstraint.ApplyTo[TActual](TActual actual)
   at NUnit.Framework.Constraints.ExactCountConstraint.ApplyTo[TActual](TActual actual)
   at NUnit.Framework.Constraints.AndConstraint.ApplyTo[TActual](TActual actual)
   at NUnit.Framework.Constraints.AndConstraint.ApplyTo[TActual](TActual actual)
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression, String message, Object[] args)
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression)
   at SomeTests.Dummy() in /path/TestFile.cs:line 115
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

Namespaces, file paths and test names have been changed to protect the innocent.

@OsirisTerje
Copy link
Member

OsirisTerje commented Jul 19, 2023

Thanks! This seems to be a bug.
I copied your repro into https://github.com/nunit/nunit.issues/tree/main/Issue4423, split it up and made a couple of more tests there. It looks like it is the ItemAt that give the problem.
It does return two different error message though:
image
and
image

The code is:
image

@OsirisTerje OsirisTerje added is:bug Investigate We will look into this labels Jul 19, 2023
@azygis
Copy link
Author

azygis commented Jul 19, 2023

I think the one that stems from .One.ItemAt is because One is returning the instance from the list to work on. At that point, it tries to use an indexer on an instance that has no indexer.

@OsirisTerje
Copy link
Member

OsirisTerje commented Jul 19, 2023

Make sense. It should however 1) give a better error message, or not sure if we can, make it a compiler error, and 2) add a nunit.analyzer warnings for this.

OsirisTerje pushed a commit that referenced this issue Oct 8, 2023
* Add check if RightContext is IndexerOperator to allow ItemAt on the right side of an And operator

* add test for IndexerOperator when on the left side is an AndOperator
@OsirisTerje OsirisTerje added this to the 4.0 milestone Oct 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants