SemanticComparisonExtensions is a .NET library that provides set of convenience methods that make it easier to compare collections and object hierarchies using SemanticComparison.
Method | Target | Description |
---|---|---|
WithPropertyMap | Likeness | Method configures likeness to compare specified source and destination properties to determine objects equality. |
WithCollectionSequenceEquals | Likeness | Method configures likeness to compare specified source and destination collections items using default equality. |
WithInnerLikeness | Likeness | Methods configures likeness to compare specified source and destination properties using specified inner likeness. |
WithInnerSpecificLikeness | Likeness | The extended version of WithInnerLikeness. Allows to specify inner likeness for derived types of properties types. |
WithCollectionInnerLikeness | Likeness | Method configures likeness to compare specified source and destination collections using inner likeness for items. |
WithCollectionInnerSpecificLikeness | Likeness | The extended version of WithCollectionInnerLikeness. Allows to specify inner likeness for derived types of item types. |
CompareCollectionsUsingLikeness | IEnumerable<T> | Method compares collection with other collection using specified likeness for items. |
CompareCollectionsUsingSpecificLikeness | IEnumerable<T> | The extended version of CompareCollectionsUsingLikeness. Allows to specify likeness for derived types of item types. |
The nuget package is available in the nuget.org feed.
https://www.nuget.org/packages/SemanticComparisonExtensions
Let's say the Invoice and Invoice dto object hierarchies need to be compared in the verification stage of the unit test.
public class Invoice
{
public string Number { get; set; }
public IList<InvoiceItem> InvoiceItems { get; set; }
public Issuer Issuer { get; set; }
}
public class InvoiceItem
{
public string ProductName { get; set; }
public int Quantity { get; set; }
}
public class Issuer
{
public string Name { get; set; }
public string Address { get; set; }
}
public class InvoiceDto
{
public string Number { get; set; }
public IList<InvoiceItemDto> Items { get; set; }
public IssuerDto Issuer { get; set; }
}
public class InvoiceItemDto
{
public string ProductName { get; set; }
public int Quantity { get; set; }
}
public class IssuerDto
{
public string Name { get; set; }
public string Address { get; set; }
}
With the semantic comparison extension methods, you can build the likeness object configured to compare all the objects in hierarchy memberwise using the syntax below.
invoice.AsSource().OfLikeness<InvoiceDto>()
.WithInnerLikeness(t => t.Issuer, s => s.Issuer)
.WithCollectionInnerLikeness(t => t.Items, s => s.InvoiceItems)
.ShouldEqual(invoiceDto);
With the WithInnerLikeness and WithCollectionInnerLikeness methods you can configure inner likeness for the property or collection items that should be used in the comparison process. If you do need to override default members evaluators you can omit the third parameter (as in the example above).
Example with the custom overrides:
invoice.AsSource().OfLikeness<InvoiceDto>()
.WithInnerLikeness(t => t.Issuer, s => s.Issuer,
likeness => likeness.Without(x => x.Address))
.WithCollectionInnerLikeness(t => t.Items, s => s.InvoiceItems,
likeness => likeness.Without(x => x.Quantity))
.ShouldEqual(invoiceDto);
Naturally you can invoke those extension methods on the inner likeness if you need to compare multi level object graph.
var value = new List<InvoiceItem>();
var other = new List<InvoiceItemDto>();
var result = value.CompareCollectionsUsingLikeness(other, likeness => likeness);
public class Parent
{
public IEnumerable<int> Numbers { get; set; }
}
The code below will cause Numbers collection to be compared item by item using default equality (Equals method).
value.AsSource().OfLikeness<Root>()
.WithCollectionSequenceEquals(r => r.Numbers)
.ShouldEqual(other);
Sometimes the class definition contains inner properties that are base types. By default WithInnerLikeness and WithCollectionInnerLikeness methods infer likeness generic type parameters from the property picker lambda expression. So, if you assign objects that inherit from the base class defined in the parent class definition the constructed likeness will be base class likeness, that doesn't include fields from the derived class.
To compare objects using likeness that operates on derived classes you need to specify those derived classes explicitly. There are separate versions of the extension methods for that purpose: WithInnerSpecificLikeness, WithCollectionInnerSpecificLikeness.
public abstract class InnerBase
{
}
public class Inner : InnerBase
{
public string Text { get; set; }
}
public class Parent
{
public InnerBase Inner { get; set; }
}
parent.AsSource().OfLikeness<Parent>()
.WithInnerSpecificLikeness(t => t.Inner, s => s.Inner, (Likeness<Inner, Inner> likeness) => likeness)
.ShouldEqual(other);
public class ProductDto
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
To compare product and product dto you need to specify mapping between ProductId and Id. You could do it this way:
product.AsSource().OfLikeness<ProductDto>()
.With(d => d.ProductId)
.EqualsWhen((s, d) => s.Id == d.ProductId)
.ShouldEqual(productDto);
You can achieve the same effect using WithPropertyMap extension method:
product.AsSource().OfLikeness<ProductDto>()
.WithPropertyMap(d => d.ProductId, s => s.Id)
.ShouldEqual(productDto);
The extensions methods provide diagnostic messages that are meant to help identify the exact item that is equal/not equal in the object graph. By default the logging is performed to the trace output. However, you can provide your own implementation of the message writer by implementing IDiagnosticsWriter interface and assigning it to the DiagnosticsWriterLocator.DiagnosticsWriter.