-
-
Notifications
You must be signed in to change notification settings - Fork 805
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
New It.Is overload for matching values using custom IEqualityComparer #1059
Comments
BTW, what's the reason |
Here's what I'm using at the moment: public static class ItExtensions
{
public static TValue ItIsEquivalentTo<TValue>(TValue expectedValue, IEqualityComparer<TValue> equalityComparer) =>
Match.Create<TValue>(value => equalityComparer.Equals(expectedValue, value));
public static TValue ItIsEquivalentTo<TValue>(TValue expectedValue) =>
ItIsEquivalentTo(expectedValue, new PropertiesEqualityComparer<TValue>());
private class PropertiesEqualityComparer<TValue> : EqualityComparer<TValue>
{
static readonly Lazy<PropertyInfo[]> _Properties = new Lazy<PropertyInfo[]>(() => typeof(TValue).GetProperties());
static IEnumerable<PropertyInfo> Properties => _Properties.Value;
static IEnumerable<object?> GetValues(TValue value) => Properties.Select(prop => prop.GetValue(value));
static IEnumerable<(object? Value1, object? Value2)> GetValues(TValue value1, TValue value2) =>
Properties.Select(prop => (prop.GetValue(value1), prop.GetValue(value2)));
public override bool Equals([AllowNull] TValue x, [AllowNull] TValue y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null)
return y == null;
else if (y == null)
return false;
return
GetValues(x, y)
.All(values => object.Equals(values.Value1, values.Value2));
}
public override int GetHashCode([DisallowNull] TValue obj)
{
if (obj == null)
return 0;
return GetValues(obj)
.Select(val => val?.GetHashCode() ?? 0)
.Aggregate((hash1, hash2) => hash1 ^ hash2);
}
}
} Usage: using static MyProject.Api.Tests.Unit.TestExtensions.ItExtensions;
...
_PerTenantUserStoreMock
.Verify(perTenantUserStore =>
perTenantUserStore.CreateAsync(ItIsEquivalentTo(perTenantUser), defaultCancellationToken),
Times.Once); |
@weitzhandler your On the one hand, your helper is generic enough that it very much looks like a feasible addition to the Moq library. On the other hand, I'm a little bit afraid of the effect it will have on new users, due to the name for the new matcher: Ideally, the matcher should end up being named However, once we have a matcher called If it weren't for that, I'd be very much in favor of your proposed addition. For the reason just given, I'm not 100% sure whether it should be in the main library. |
Hey @stakx and thanks for you dedication and diligence!
I agree. I actually borrowed the name from FluentAssertions😋, but it works differently there, it compares the entire graph, which I think might be too much for this scope/sprint. So can I PR this addition with some tests? |
I would merge a PR only if I felt that my concerns regarding the matcher's name (see above) were reasonably addressed, or shown to be unfounded. That hasn't happened yet, so perhaps it's better if you waited a little longer (it would be a shame to have you spend time on a PR that then doesn't get merged). Let's see if more opinions/arguments are added to this discussion. Regarding that hypothetical PR, are we in agreement that it would only encompass the matcher method ( |
I don't have a strong opinion about the naming at all. Actually I'd simply add another overload to the existing
I agree. And in any case we can continue the discussion of whether we provide an implementation to that after the basic PR is implemented. |
Please have a look at this commit: moq/moq.spikes@fe9ccf0, and let me know if I'm headed the right direction. BTW, I thought I should implement this in moq5, but then I see that the |
You'll need to decide whether you want to work on Moq v4 (this repository) or Moq v5 (a whole different beast, and residing in the repository where you committed). Like I already said above, I don't think spending time on a PR makes much sense unless we know we actually want to do this, so please understand that I'm not going to review at this time just yet. |
Let's decide on an API then if at all then. How about just one addition:
How does that sound to you? Having only one overload that takes a comparer, would prevent users getting confused about using an inline constant. Also consider add this overload that takes a comparer to the other ones ( |
But I understand it's designed differently in v5 (it was hidden in intellisense and placed under "legacy"). What's the new recommended way of using this instead of |
Yes, this looks reasonable. While it does give
I believe in Moq 5 you'd do |
OK great, glad we're on the same page then. What about the rest of the functions, like
Well for now lemme just stick to 4 then. Should it turn out to be that awesome, and you guys might consider porting it into 5 lol... |
Good thought. That would perhaps be a good idea for |
Thanks for the warning. My intention was actually to add a new one and port the existing one to it using |
That should be fine. I was mostly concerned about the method's signatures, not their implementation (as long as it still behaves in exactly the same way). |
Nah, won't be breaking any code 😱 |
I'm looking for a way to easily compare multiple fields in an incoming class, to a given class.
Current code:
Desired code:
I encounter this request over and over and it's compelling to me. I'm willing to contribute to this.
The text was updated successfully, but these errors were encountered: