Skip to content
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

Use IEquatable instead of Equals where possible #226

Open
niklasda opened this issue Apr 19, 2016 · 5 comments
Open

Use IEquatable instead of Equals where possible #226

niklasda opened this issue Apr 19, 2016 · 5 comments
Labels
feature-request Request for a new NSubstitute feature good first issue For smaller tasks that do not require in-depth knowledge of the code base help wanted Core team needs help with this! If you've got some time, please volunteer to take a look.

Comments

@niklasda
Copy link

niklasda commented Apr 19, 2016

I run in to an expected test failure when I was substituting my Dictionary<> based cache.
The key I use for the dictionary is a composite of 2 values, and we know that the key object has to implement IEquatable<> in order to find the value in the dictionary again. This works:

ICompositeKeyCache cache = new CompositeKeyCache();
cache.Add(new CompositeKey(Constants.KeyPart1, Constants.KeyPart2), Constants.ReturnValue);
string retVal = cache.Get(new CompositeKey(Constants.KeyPart1, Constants.KeyPart2));

Unfortunately the substitute I used for testing this failed in this case:

var cacheSub = Substitute.For<ICompositeKeyCache>();
cacheSub.Get(new CompositeKey(Constants.KeyPart1, Constants.KeyPart)).Returns(Constants.ReturnValue);
string retVal = cacheSub.Get(new CompositeKey(Constants.KeyPart1, Constants.KeyPart2));

After some investigation I learned that in order for the substitute to work the key objects also has to override the Equals() method. Overriding the Equals() method also works in the actual dictionary.

Somewhat annoying when the test fails and actual code works. I think implementing IEquatable<> should also work with NSubstitute.

see complete code here:
https://github.com/niklasda/NSubstituteTestCaseProject

Keep up the good work

@dtchepak
Copy link
Member

Thanks for the comprehensive repro case!

I haven't had a chance to dig into the NSubstitute side of this yet, but in the meantime I thought I'd offer a couple mitigations. First, I think the duplicate code in the object.Equals override can be simplified to:

        public override bool Equals(object obj)
        {
            var other = obj as CompositeKeyFail;
            return (other != null) && Equals(other);
        }

Secondly, if you have R# (or similar) you can generate equality members. In my VS it is ALT+Insert, and that will generate the IEquatable.Equals with an object.Equals that delegates to it.

This doesn't fix the root of the issue you are having but hopefully will make things easier for you while using the current NSub release.

@dtchepak
Copy link
Member

dtchepak commented Apr 20, 2016

Implementation note:

I think the EqualsArgumentMatcher is the relevant class.

This has given us problems before (with related tests) so need to make sure we don't break anything.

(Backup of original report repo: https://github.com/dtchepak/NSubstituteTestCaseProject)

@dtchepak dtchepak added the feature-request Request for a new NSubstitute feature label Oct 7, 2017
@dtchepak dtchepak changed the title Fails to use IEquatable instead of Equals Use IEquatable instead of Equals Oct 7, 2017
@dtchepak dtchepak changed the title Use IEquatable instead of Equals Use IEquatable instead of Equals where possible Oct 7, 2017
@dtchepak
Copy link
Member

Simple repro:

namespace NSubWorkshop
{
    using System;
    using NSubstitute;
    using Xunit;

    public class IntWrap : IEquatable<IntWrap>
    {
        public IntWrap(int value) {
            Value = value;
        }

        public int Value { get; set; }

        public int CompareTo(IntWrap other) {
            return Value.CompareTo(other.Value);
        }

        // Uncommenting this causes test to pass:
        //public override bool Equals(object obj) {
        //    return Equals(obj as IntWrap);
        //}

        public bool Equals(IntWrap other) {
            return other != null &&
                   Value == other.Value;
        }

        public override int GetHashCode() {
            return -1937169414 + Value.GetHashCode();
        }
    }

    public interface ISample
    {
        void Use(IntWrap value);
    }

    public class SampleFixture
    {

        [Fact]
        public void Sample() {
            var value1 = new IntWrap(42);
            var value2 = new IntWrap(42);
            var sub = Substitute.For<ISample>();

            sub.Use(value1);

            sub.Received().Use(value2);

        }
    }
}

@dtchepak dtchepak added the help wanted Core team needs help with this! If you've got some time, please volunteer to take a look. label Aug 29, 2019
@alexandrnikitin alexandrnikitin added the good first issue For smaller tasks that do not require in-depth knowledge of the code base label Aug 29, 2019
@zvirja
Copy link
Contributor

zvirja commented Sep 22, 2019

@dtchepak @alexandrnikitin Just want to clarify whether we still want to support this issue. As David stated above, if struct would override the Equals() method it would successfully work. To my knowledge, the IEquatable<> interface is an auxiliary util to avoid boxing and when it's implemented it's mandatory to override the Equals() method as well.

So if people are following best practices, they are fully compatible with NSubstitute. Does it make sense to specifically handle scenario when type itself is inconsistent (reports different equality status depending on the method is used)?

Would be also happy to see that I'm overlooking something.

@dtchepak
Copy link
Member

@zvirja When you put it like that I'm happier to just use Equals. It is much easier to understand "all comparisons will be done with Equals" than "comparisons will be done with IEquatable if it exists otherwise Equals and if they disagree IEquatable will be preferred".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request for a new NSubstitute feature good first issue For smaller tasks that do not require in-depth knowledge of the code base help wanted Core team needs help with this! If you've got some time, please volunteer to take a look.
Projects
None yet
Development

No branches or pull requests

4 participants