How to use BeEquivalentTo for 2 DTOs, both having an Id property, but one of type Guid, the other with this same Guid converted to string #2672
-
Hello, Given these 2 types, I am struggling to validate that 2 instances of these types are equivalent using public class PocoContract
{
public Guid Id { get; set; }
public Decimal Amount { get; set; }
}
public class ProtobufContract
{
public string Id { get; set; }
public float Amount { get; set; }
} Let us suppose the following test, where all assertions that I'd really like to write fail. [Fact]
public void PocoVsProto()
{
var poco = new PocoContract { Id = Guid.NewGuid(), Amount = 123M };
var proto = new ProtobufContract { Id = poco.Id.ToString(), Amount = (float) poco.Amount };
// following assertion fails with Expected property poco.Id to be System.String, but found System.Guid.
poco.Should().BeEquivalentTo(proto);
// following assertion fails with Expected property proto.Id to be {08081ed6-5462-44ac-9d06-264809a8d5c4}, but found "08081ed6-5462-44ac-9d06-264809a8d5c4".
proto.Should().BeEquivalentTo(poco);
// following assertion fails with Expression <pc.Id.ToString()> cannot be used to select a member. (Parameter 'expression')
proto.Should().BeEquivalentTo(poco, options => options
.WithMapping<ProtobufContract>(pc => pc.Id.ToString(), pb => pb.Id)
.WithMapping<ProtobufContract>(pc => pc.Amount, pb => (decimal) pb.Amount)); // not necessary as float/decimal conversion is handled by .BeEquivalentTo()
// following assertions obviously pass, but ugh...
proto.Id.Should().Be(poco.Id.ToString());
proto.Amount.Should().Be((float) poco.Amount); // again, not necessary as float/decimal conversion is handled by .BeEquivalentTo()
proto.Should().BeEquivalentTo(poco, options => options.Excluding(o => o.Id));
} I understand that I was trying to abuse the Thank you! François |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 15 replies
-
What happens with: .WithMapping<ProtobufContract>(pc => pc.Id, pb => pb.Id.ToString()) or other way round .WithMapping<ProtobufContract>(pc => Guid.Parse(pc.Id), pb => pb.Id) (As I understand the example |
Beta Was this translation helpful? Give feedback.
-
That does not seem to be of concern. I need to compare a string and a guid, not refine either a string-to-string or a guid-to-guid assertion. Or am I missing something? |
Beta Was this translation helpful? Give feedback.
-
Did you try |
Beta Was this translation helpful? Give feedback.
-
I played around a bit... And nothing worked except of: proto.Should().BeEquivalentTo(poco, opt => opt
.Using(new GuidAndStringEquivalencyStep()));
private class GuidAndStringEquivalencyStep : IEquivalencyStep
{
public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
{
if (comparands.Subject is ProtobufContract a && comparands.Expectation is PocoContract b)
{
a.Id.Should().Be(b.Id.ToString());
return EquivalencyResult.AssertionCompleted;
}
return EquivalencyResult.ContinueWithNext;
}
} The only "drawback" is: you are tied to this specific types.. |
Beta Was this translation helpful? Give feedback.
-
That's because |
Beta Was this translation helpful? Give feedback.
-
Well I played a little more with the material that @IT-VBFK gave me. The following is the least ugly solution that I could come with... but I am not even sure it is worth the trouble: public class PocoContract
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
}
public class ProtobufContract
{
public string Id { get; set; }
public float Amount { get; set; }
public string Currency { get; set; }
}
public class ContractFixture
{
[Fact]
public void MessagesAreEquivalent()
{
var poco = new PocoContract { Id = Guid.NewGuid(), Amount = 123.45M, Currency = "EUR" };
var proto = new ProtobufContract { Id = poco.Id.ToString(), Amount = 123.45f, Currency = "EUR" };
poco.Should().BeEquivalentTo(proto, static options => options.Using(new PocoAndProtobufContractEquivalencyStep()));
proto.Should().BeEquivalentTo(poco, static options => options.Using(new PocoAndProtobufContractEquivalencyStep()));
}
}
internal class PocoAndProtobufContractEquivalencyStep : IEquivalencyStep
{
public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
{
var poco = comparands.Subject as PocoContract ?? comparands.Expectation as PocoContract;
var proto = comparands.Subject as ProtobufContract ?? comparands.Expectation as ProtobufContract;
if (poco is null || proto is null) return EquivalencyResult.ContinueWithNext;
poco.Id.Should().Be(proto.Id);
poco.Should().BeEquivalentTo(proto, static options => options.Excluding(static r => r.Id));
return EquivalencyResult.AssertionCompleted;
}
} The following do not work: poco.Should().BeEquivalentTo(proto, static options => options.WithAutoConversion());
proto.Should().BeEquivalentTo(poco, static options => options.WithAutoConversion()); Out of curiosity, the poco.Id.Should().Be(proto.Id);
proto.Id.Should().Be(poco.Id.ToString());
poco.Amount.Should().Be((decimal) proto.Amount);
proto.Amount.Should().Be((float) poco.Amount); @dennisdoomen, would you care to comment, as comparing a It would be nice if we could pass a pair of conversion lambdas to Thank you! |
Beta Was this translation helpful? Give feedback.
Well I played a little more with the material that @IT-VBFK gave me. The following is the least ugly solution that I could come with... but I am not even sure it is worth the trouble: