Joe
Joe

Reputation: 856

NUnit CollectionAssert.AreEqual(expected,actual) vs Assert.IsTrue(expected.SequenceEqual(actual))

I have some objects that get placed into a queue. All of the objects in the queue implement the same base interface, which also requires that they implement IEquatable<>.

I want to verify that the correct objects are being placed in the queue in the correct order.

When I write a test that makes the assertion CollectionAssert.AreEqual(expected,actual), the test fails, stating that, while the queue is the expected length, it differs at index 0.

However, when I write the test assertion as Assert.IsTrue(expected.SequenceEqual(actual)), the test passes.

It seems to me that these two assertions should be the same, but apparently they are quite different.

NUnit's website isn't very detailed about the collection assert methods, and the Linq documentation, while more detailed, doesn't really help without being able to compare to what NUnit is doing with the collection assert method.

How do these two assertions differ?

Edit - Adding detail

Here is the code under test as well as the two tests that I would expect to have the same result.

public enum ArgumentTypeEnum
{
    SpiceworksDbName,
    DestinationDbName,
    DestinationServerName
}

public interface IArgument : IEquatable<IArgument>
{
}

public interface ICommandArgument : IArgument
{
    ArgumentTypeEnum ArgumentType { get; }
}

internal interface IValueArgument : IArgument
{
    String ArgumentValue { get; }
}

public class CommandArgument : ICommandArgument
{
    public CommandArgument(ArgumentTypeEnum argumentType) {
        ArgumentType = argumentType;
    }

    public ArgumentTypeEnum ArgumentType { get; private set; }

    public bool Equals(IArgument other)
    {
        if (other == null)
        {
            return false;
        }

        var otherAsCommandArg = other as CommandArgument;
        if (otherAsCommandArg == null)
        {
            return false;
        }

        return ArgumentType == otherAsCommandArg.ArgumentType;
    }
}

public class ValueArgument : IValueArgument
{
    public ValueArgument(String argumentValue)
    {
        ArgumentValue = argumentValue;
    }

    public string ArgumentValue { get; private set; }

    public bool Equals(IArgument other)
    {
        if (other == null)
        {
            return false;
        }

        var otherAsValueArg = other as ValueArgument;
        if (otherAsValueArg == null)
        {
            return false;
        }

        return ArgumentValue == otherAsValueArg.ArgumentValue;
    }
}

public class Tokenizer
{
     public Queue<IArgument> TokenizeArguments(string[] args) {
        var tokenQueue = new Queue<IArgument>();
        args.ToList().ForEach((arg) => {
                if (arg.StartsWith("-"))
                {
                    switch (arg)
                    {
                        case "-sd":
                            tokenQueue.Enqueue(new CommandArgument(ArgumentTypeEnum.SpiceworksDbName));
                            break;
                        case "-dd":
                            tokenQueue.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationDbName));
                            break;
                        case "-ds":
                            tokenQueue.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationServerName));
                            break;
                        default:
                            tokenQueue.Enqueue(new ValueArgument(arg));
                            break;
                    }
                }
                else
                {
                    tokenQueue.Enqueue(new ValueArgument(arg));
                }
            });
        return tokenQueue;
    }
}

[TestFixture]
public class TokenizerSpecs
{
    ///Passes
    [Test] 
    public void Tokenizer_MultipleCommandsAndValues_TokenizesAsExpected1() {
        var args = new[]
        {
            "-sd", @"\\some\Directory\foo.db", "-dd", "some database name", "-ds", "Server.name", "somerandomarg",
            "-unreconized"
        };
        var t = new Tokenizer();
        var actualResult = t.TokenizeArguments(args);

        var expectedResult = new Queue<IArgument>();
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.SpiceworksDbName));
        expectedResult.Enqueue(new ValueArgument(@"\\some\Directory\foo.db"));
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationDbName));
        expectedResult.Enqueue(new ValueArgument("some database name"));
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationServerName));
        expectedResult.Enqueue(new ValueArgument("Server.name"));
        expectedResult.Enqueue(new ValueArgument("somerandomarg"));
        expectedResult.Enqueue(new ValueArgument("-unreconized"));

        Assert.IsTrue(expectedResult.SequenceEqual(actualResult));
    }

    ///Fails
    [Test]
    public void Tokenizer_MultipleCommandsAndValues_TokenizesAsExpected2()
    {
        var args = new[]
        {
            "-sd", @"\\some\Directory\foo.db", "-dd", "some database name", "-ds", "Server.name", "somerandomarg",
            "-unreconized"
        };
        var t = new Tokenizer();
        var actualResult = t.TokenizeArguments(args);

        var expectedResult = new Queue<IArgument>();
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.SpiceworksDbName));
        expectedResult.Enqueue(new ValueArgument(@"\\some\Directory\foo.db"));
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationDbName));
        expectedResult.Enqueue(new ValueArgument("some database name"));
        expectedResult.Enqueue(new CommandArgument(ArgumentTypeEnum.DestinationServerName));
        expectedResult.Enqueue(new ValueArgument("Server.name"));
        expectedResult.Enqueue(new ValueArgument("somerandomarg"));
        expectedResult.Enqueue(new ValueArgument("-unreconized"));

        CollectionAssert.AreEqual(expectedResult, actualResult);
    }
}

Upvotes: 4

Views: 1635

Answers (1)

Gert Arnold
Gert Arnold

Reputation: 109255

I've taken a look at NUnit's source code and it seems you've got a bug here. The comparison is done by NUnitEqualityComparer.EnumerablesEqual which subsequently compares each element by NUnitEqualityComparer.ObjectsEqual. We can do simple test using this object:

var n = new NUnitEqualityComparer();
var tolerance = Tolerance.Zero;
var equal = n.AreEqual(new CommandArgument(ArgumentTypeEnum.SpiceworksDbName),
                       new CommandArgument(ArgumentTypeEnum.SpiceworksDbName),
                       ref tolerance);

This test returns false!

Somehow, ObjectsEqual doesn't end up in the object's Equals method. It would require debugging against the source code to find out why, but this test sufficiently proves that a bug is involved.

Upvotes: 3

Related Questions