我使用FluentAssertions比较使用Should().BeEquivalentTo()
的两个对象,其中一个对象是EF动态代理。但是,在使用ShouldBeEquivalentTo
时,5.0.0中ShouldAllBeEquivalentTo
和RespectingRuntimeTypes
(#593)的统一似乎已经破坏了功能。除非我为对象图中的每个类型显式添加ComparingByMembers
,否则不再比较已声明类型的派生类型的属性成员。有没有办法使用其他设置?
答案 0 :(得分:1)
为了解决这个问题,我编写了以下扩展方法,但在动态代理上运行时修复派生类型的问题似乎很麻烦:
public static class FluentAssertionsExtensions
{
/// <summary>
/// Extends the functionality of <see cref="EquivalencyAssertionOptions{TExpectation}" />.ComparingByMembers by recursing into the entire object graph
/// of the T or passed object and marks all property reference types as types that should be compared by its members even though it may override the
/// System.Object.Equals(System.Object) method. T should be used in conjunction with RespectingDeclaredTypes. The passed object should be used in
/// conjunction with RespectingRuntimeTypes.
/// </summary>
public static EquivalencyAssertionOptions<T> ComparingByMembersRecursive<T>(this EquivalencyAssertionOptions<T> options, object obj = null)
{
var handledTypes = new HashSet<Type>();
var items = new Stack<(object obj, Type type)>(new[] { (obj, obj?.GetType() ?? typeof(T)) });
while (items.Any())
{
(object obj, Type type) item = items.Pop();
Type type = item.obj?.GetType() ?? item.type;
if (!handledTypes.Contains(type))
{
handledTypes.Add(type);
foreach (PropertyInfo pi in type.GetProperties())
{
object nextObject = item.obj != null ? pi.GetValue(item.obj) : null;
Type nextType = nextObject?.GetType() ?? pi.PropertyType;
// Skip string as it is essentially an array of chars, and needn't be processed.
if (nextType != typeof(string))
{
if (nextType.GetInterface(nameof(IEnumerable)) != null)
{
nextType = nextType.HasElementType ? nextType.GetElementType() : nextType.GetGenericArguments().First();
if (nextObject != null)
{
// Look at all objects in a collection in case any derive from the collection element type.
foreach (object enumObj in (IEnumerable)nextObject)
{
items.Push((enumObj, nextType));
}
continue;
}
}
items.Push((nextObject, nextType));
}
}
if (type.IsClass && type != typeof(string))
{
// ReSharper disable once PossibleNullReferenceException
options = (EquivalencyAssertionOptions<T>)options
.GetType()
.GetMethod(nameof(EquivalencyAssertionOptions<T>.ComparingByMembers))
.MakeGenericMethod(type).Invoke(options, null);
}
}
}
return options;
}
}
这应该像这样调用:
foo.Should().BeEquivalentTo(bar, o => o
.RespectingRuntimeTypes()
.ComparingByMembersRecursive(foo)
.ExcludingMissingMembers());
答案 1 :(得分:0)
我最近使用Microsoft.EntityFrameworkCore.Proxies
遇到了同样的问题。就我而言,我必须比较持久属性,而忽略比较其余甚至导航属性。
该解决方案正在实现Intercafe FluentAssertions.Equivalency.IMemberSelectionRule
以排除不必要的属性。
public class PersistentPropertiesSelectionRule<TEntity> : IMemberSelectionRule
where TEntity : class
{
public PersistentPropertiesSelectionRule(DbContext dbContext) =>
this.dbContext = dbContext;
public bool IncludesMembers => false;
public IEnumerable<SelectedMemberInfo> SelectMembers(
IEnumerable<SelectedMemberInfo> selectedMembers,
IMemberInfo context,
IEquivalencyAssertionOptions config)
{
var dbPropertyNames = dbContext.Model
.FindEntityType(typeof(TEntity))
.GetProperties()
.Select(p => p.Name)
.ToArray();
return selectedMembers.Where(x => dbPropertyNames.Contains(x.Name));
}
public override string ToString() => "Include only persistent properties";
readonly DbContext dbContext;
}
然后编写扩展方法可以帮助方便使用并提高可读性。扩展方法可以类似于以下代码。
public static class FluentAssertionExtensions
{
public static EquivalencyAssertionOptions<TEntity> IncludingPersistentProperties<TEntity>(this EquivalencyAssertionOptions<TEntity> options, DbContext dbContext)
where TEntity : class
{
return options.Using(new PersistentPropertiesSelectionRule<TEntity>(dbContext));
}
}
最后,您可以像下面的代码一样在测试中调用扩展方法。
// Assert something
using (var context = DbContextFactory.Create())
{
var myEntitySet = context.MyEntities.ToArray();
myEntitySet.Should().BeEquivalentTo(expectedEntities, options => options
.IncludingPersistentProperties(context)
.Excluding(r => r.MyPrimaryKey));
}
此实现解决了我的问题,代码看起来整洁。