如何断言所有选定的属性都已设置(非空或空)

Tar*_*asB 7 .net c# fluent-assertions

我想验证(断言)我的DTO对象上的某些属性是否已设置.我试图用Fluent Assertions来做,但以下代码似乎不起作用:

mapped.ShouldHave().Properties(
    x => x.Description,
    ...more
    x => x.Id)
    .Should().NotBeNull(); 
Run Code Online (Sandbox Code Playgroud)

是否可以通过Fluent Assertions或其他工具实现这一目标?Fluent断言具有ShouldBeEquivalentTo,但实际上我只关心那些是不是空/空,所以我无法使用.

当然我可以在每个属性级别上做一个Assert,但是对一些更优雅的方式感兴趣.

Ily*_*nov 6

实际上,Properties方法return PropertiesAssertion,仅具有EqualTo用于相等比较的方法。没有 NotEqualTo方法或NotNull。在您的测试中,您预期PropertiesAssertion不会是null,这就是为什么它将始终通过的原因。

您可以实现一个AssertionHelper静态类并传递一个Funcs 数组,用于验证对象。这是一个非常幼稚的实现,您不会得到很好的错误报告,但我只是展示了总体思路

public static void CheckAllPropertiesAreNotNull<T>(this T objectToInspect, 
                                                params Func<T, object>[] getters)
{
    if (getters.Any(f => f(objectToInspect) == null))
        Assert.Fail("some of the properties are null");
}
Run Code Online (Sandbox Code Playgroud)

现在此测试将失败并some of the properties are null显示消息

var myDto = new MyDto();

myDto.CheckAllPropertiesAreNotNull(x => x.Description, 
                                   x => x.Id); 
Run Code Online (Sandbox Code Playgroud)

该解决方案存在两个问题

  • 如果Id属性为值类型,getter(objectToInspect) == null则始终为false
  • 您不会获得为空的属性的名称,而只是得到一条通用消息。

要解决第一点,您可以:

  • 创建一个重载到CheckAllPropertiesAreNotNull,每个重载将具有不同数量的Generic Func<TInput, TFirstOutput> firstGetter,然后将每个getter的返回值与对应的default(TFirstOutput)
  • 使用Activator创建默认实例并调用Equals

我将告诉您第二种情况。您可以创建一个IsDefault方法,该方法将接受类型为参数的参数object(请注意,这可以是装箱的int):

private static bool IsDefault(this object value)
{
    if (value == null)
        return true;

    if (!value.GetType().IsValueType) //if a reference type and not null
        return false;

    //all value types should have a parameterless constructor
    var defaultValue = Activator.CreateInstance(value.GetType());
    return value.Equals(defaultValue);
}
Run Code Online (Sandbox Code Playgroud)

现在,我们的总体代码中,该处理程序的值类型将如下所示:

public static void CheckAllPropertiesAreNotDefault<T>(this T objectToInspect, 
                                                params Func<T, object>[] getters)
{
    if (getters.Any(f => f(objectToInspect).IsDefault()))
        Assert.Fail("some of the properties are not null");
}
Run Code Online (Sandbox Code Playgroud)

要解决第二点,您可以传递一个Expression<Func<T, object>>[] getters,其中将包含有关称为属性的信息。创建一个方法GetName,该方法将接受Expression<Func<T, object>>并返回调用的属性名称

public static string GetName<T>(Expression<Func<T, object>> exp)
{
    var body = exp.Body as MemberExpression;

    //return type is an object, so type cast expression will be added to value types
    if (body == null) 
    {
        var ubody = (UnaryExpression)exp.Body;
        body = ubody.Operand as MemberExpression;
    }

    return body.Member.Name;
}
Run Code Online (Sandbox Code Playgroud)

现在生成的代码如下所示:

public static void CheckAllPropertiesAreNotDefault<T>(this T objectToInspect, 
                                     params Expression<Func<T, object>>[] getters)
{
    var defaultProperties = getters.Where(f => f.Compile()(objectToInspect).IsDefault());

    if (defaultProperties.Any())
    {
        var commaSeparatedPropertiesNames = string.Join(", ", defaultProperties.Select(GetName));
        Assert.Fail("expected next properties not to have default values: " + commaSeparatedPropertiesNames);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在给我打电话

myDto.CheckAllPropertiesAreNotDefault(x => x.Description,
                                      x => x.Id);
Run Code Online (Sandbox Code Playgroud)

我懂了

预期的下一个属性不具有默认值:说明,ID

错误信息。在我的Dto中Description是一个string并且Id是值类型int。如果我将该属性设置为一些非默认值,则不会出现错误,并且测试将通过。