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

Idiomatic assertions for IEqualityComparer<T> #1194

Merged
merged 5 commits into from Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions Src/Idioms/EqualityComparerAssertion.cs
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly.
/// </summary>
public class EqualityComparerAssertion : CompositeIdiomaticAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerAssertion(ISpecimenBuilder builder)
: base(CreateChildAssertions(builder))
{
}

private static IEnumerable<IIdiomaticAssertion> CreateChildAssertions(ISpecimenBuilder builder)
{
yield return new EqualityComparerEqualsSelfAssertion(builder);
yield return new EqualityComparerEqualsSymmetricAssertion(builder);
yield return new EqualityComparerEqualsTransitiveAssertion(builder);
yield return new EqualityComparerGetHashCodeAssertion(builder);
yield return new EqualityComparerEqualsNullAssertion(builder);
}
}
}
66 changes: 66 additions & 0 deletions Src/Idioms/EqualityComparerEqualsAssertion.cs
@@ -0,0 +1,66 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// A base class for building classes that encapsulate a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly.
/// </summary>
public abstract class EqualityComparerEqualsAssertion : IdiomaticAssertion
{
/// <summary>
/// Gets the builder supplied by the constructor.
/// </summary>
public ISpecimenBuilder Builder { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
protected EqualityComparerEqualsAssertion(ISpecimenBuilder builder)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
}

/// <summary>
/// Forwards the call to <see cref="VerifyEquals"/> if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
public override void Verify(MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (!IsEqualityComparerEqualsMethod(methodInfo)) return;

this.VerifyEquals(methodInfo, methodInfo.GetParameters()[0].ParameterType);
}

/// <summary>
/// Abstract method used to verify if the method implements correctly <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected abstract void VerifyEquals(MethodInfo methodInfo, Type argumentType);

private static bool IsEqualityComparerEqualsMethod(MethodInfo methodInfo)
{
return methodInfo is { Name: nameof(IEqualityComparer.Equals), ReflectedType: { } type }
&& methodInfo.GetParameters().Length == 2
&& type.ImplementsGenericInterfaceDefinition(typeof(IEqualityComparer<>))
&& type.ImplementsGenericInterface(
typeof(IEqualityComparer<>),
methodInfo.GetParameters()[0].ParameterType);
}
}
}
59 changes: 59 additions & 0 deletions Src/Idioms/EqualityComparerEqualsNullAssertion.cs
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, null) should return false.
/// </summary>
public class EqualityComparerEqualsNullAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsNullAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsNullAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, null) should return false`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));
if (methodInfo.ReflectedType!.IsValueType) return;

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var testSubject = this.Builder.CreateAnonymous(argumentType);

var result = (bool)methodInfo.Invoke(comparer, new[] { testSubject, null });

if (result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, null) should return false.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
58 changes: 58 additions & 0 deletions Src/Idioms/EqualityComparerEqualsNullNullAssertion.cs
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(null, null) should return true.
/// </summary>
public class EqualityComparerEqualsNullNullAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsNullNullAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsNullNullAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(null, null) should return true`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));
if (methodInfo.ReflectedType!.IsValueType) return;

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);

var result = (bool)methodInfo.Invoke(comparer, new object[] { null, null });

if (!result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(null, null) should return true.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
58 changes: 58 additions & 0 deletions Src/Idioms/EqualityComparerEqualsSelfAssertion.cs
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, x) should return true.
/// </summary>
public class EqualityComparerEqualsSelfAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsSelfAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsSelfAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, x) should return true`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var testSubject = this.Builder.CreateAnonymous(argumentType);

var result = (bool)methodInfo.Invoke(comparer, new[] { testSubject, testSubject });

if (!result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, x) should return true.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
60 changes: 60 additions & 0 deletions Src/Idioms/EqualityComparerEqualsSymmetricAssertion.cs
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, y) should return same as Equals(y, x).
/// </summary>
public class EqualityComparerEqualsSymmetricAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsSymmetricAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsSymmetricAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, y) should return same as Equals(y, x)`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var firstTestSubject = this.Builder.CreateAnonymous(argumentType);
var secondTestSubject = this.Builder.CreateAnonymous(argumentType);

var directResult = (bool)methodInfo.Invoke(comparer, new[] { firstTestSubject, secondTestSubject });
var invertedResult = (bool)methodInfo.Invoke(comparer, new[] { secondTestSubject, firstTestSubject });

if (directResult != invertedResult)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, y) should return same as Equals(y, x).",
methodInfo.ReflectedType!.FullName));
}
}
}
}