Fluentassertions: [Question/Bug] Usage of BeEquivalentTo with RSAParameters

Created on 1 Dec 2020  路  4Comments  路  Source: fluentassertions/fluentassertions

Before you file a bug, have you:

  • Tried upgrading to newest version of Fluent Assertions, to see if your issue has already been resolved and released?
    Already at the latest version (5.10.3)

  • Checked existing open and closed issues, to see if the issue has already been reported?
    Did a quick search and found nothing of interest.

  • Tried reproducing your problem in a new isolated project?
    Yes, reproducible.

  • Read the documentation?
    Yes, which may hint to use ComparingByMembers.

  • Considered if this is a general question and not a bug?. For general questions please use StackOverflow.
    I am not sure if it should be categorized as the former or the later.

Description

RSAParameters equivalency check seems to fail, even though the parameters are exactly the same if you compare the original parameters and the compared ones. It succeeds if we use the ComparingByMembers<RSAParameters>() option, even though RSAParameters seems to only hold public properties as byte[]:

public struct RSAParameters
{
    public byte[]? D;
    public byte[]? DP;
    public byte[]? DQ;
    public byte[]? Exponent;
    public byte[]? InverseQ;
    public byte[]? Modulus;
    public byte[]? P;
    public byte[]? Q;
}

Complete minimal example reproducing the issue

I have the following extension method to do a deep copy of an object, using Newtonsoft.Json:

public static class SystemExtensions
    {
        public static TObject DeepCopy<TObject>(this TObject @object)
            where TObject : class, new()
        {
            string serializedObject = JsonConvert.SerializeObject(@object);

            return JsonConvert.DeserializeObject<TObject>(serializedObject);
        }
    }

And the following test class, in a xUnit test project, where RsaContainer holds RSAParameters as a public property:

using System.Security.Cryptography;
using FluentAssertions;
using UnitTests.Common.Extensions;
using Xunit;

namespace RsaParametersTest
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            // Arrange
            using var rsa = RSA.Create();

            var original = new RsaContainer
            {
                Parameters = rsa.ExportParameters(true)
            };

            RsaContainer compared = original.DeepCopy();

            // Act & Assert

            // Works
            original.Should().BeEquivalentTo(compared, opts => opts.ComparingByMembers<RSAParameters>());

            // "Broken"
            original.Should().BeEquivalentTo(compared);
        }

        private class RsaContainer
        {
            public RSAParameters Parameters { get; set; }
        }
    }
}

Expected behavior:

Considering RSAParameters doesn't override ToEqual, I would expect BeEquivalentTo to work without any overrides.

image

Actual behavior:

If equivalency assertion options are left untouched, the comparison fails and the test breaks.

image

Versions

  • Which version of Fluent Assertions are you using? 5.10.3
  • Which .NET runtime and version are you targeting? .NET Core 2.2, but the issue is also there for .NET Core 3.1.

Additional Information

N/A

Most helpful comment

I've already seen this, but there is no easy way to setup global options with xUnit.

For people that would look for an alternative, I've implemented an extension method to "patch" that behavior:

Setup

public static EquivalencyAssertionOptions<TComparedObject> ConfigureRsaParametersEquivalencyCheck<TComparedObject>(this EquivalencyAssertionOptions<TComparedObject> opts)
{
   return opts.ComparingByMembers<RSAParameters>();
}

Usage

var original = new A();
var compared = new A();

compared.Should().BeEquivalentTo(original, opts => opts.ConfigureRsaParametersEquivalencyCheck());

All 4 comments

RSAParameters is a struct, which means it'll implement Equals automatically by comparing the values of the properties. But since those properties are arrays, and arrays themselves do not override Equals, you end up doing a reference equality check between two arrays, which will never match.

The ComparingByMembers option ensures that FA will do the comparison of the arrays itself.

@dennisdoomen I see, thanks for the clarification! Is there a way to configure this globally as part of a xUnit project that you would know of?

Yes, you can set this as a global option as explained here.

I've already seen this, but there is no easy way to setup global options with xUnit.

For people that would look for an alternative, I've implemented an extension method to "patch" that behavior:

Setup

public static EquivalencyAssertionOptions<TComparedObject> ConfigureRsaParametersEquivalencyCheck<TComparedObject>(this EquivalencyAssertionOptions<TComparedObject> opts)
{
   return opts.ComparingByMembers<RSAParameters>();
}

Usage

var original = new A();
var compared = new A();

compared.Should().BeEquivalentTo(original, opts => opts.ConfigureRsaParametersEquivalencyCheck());
Was this page helpful?
0 / 5 - 0 ratings