私はこの問題に時間を費やしてきました。@Dennis によって提案された解決策は、いくつかの理由で正しく機能しません。これは、次の回避策よりも非常に近く、はるかにクリーンであるため、残念です。Dennis メソッドが機能しない主な理由は、アサーション ルールが適用される前に、ReferenceEqualityEquivalencyStep が null 値を処理することです。2 番目の理由は、.When( info => true ) を使用することで、ネストされたプロパティと配列要素をテストする機能を削除することです。それを回避する方法は .When( info => !info.RuntimeType.IsComplexType() && !(info.RuntimeType is of type IEnumerable) ) のようなものですが、それはテストされる値が null でない場合にのみ適用されます. 問題は、ISubjecInfo が現在のサブジェクトへのアクセスを許可していないことです。
とにかく、ここに問題に対する私の解決策があります。私がすべてを考えていない可能性は非常に高いです。
namespace FluentAssertions
{
public class SimpleIsNotDefaultEquivalencyStep : IEquivalencyStep
{
public bool CanHandle(EquivalencyValidationContext context, IEquivalencyAssertionOptions config)
{
return true;
}
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator structuralEqualityValidator, IEquivalencyAssertionOptions config)
{
context.Subject.Should().NotBeDefault( context.Reason, context.ReasonArgs );
return true;
}
}
public static class FluentAssertionsDefaultnessExtensions
{
private static bool IsDefault( object value, bool orValueTypeDefault = false )
{
if( value == null )
{
return true;
}
Type t = value.GetType();
t = orValueTypeDefault ? Nullable.GetUnderlyingType( t ) ?? t : t;
if( t.IsValueType )
{
object defaultValue = Activator.CreateInstance( t );
return value.Equals( defaultValue );
}
else if( value is string )
{
return string.IsNullOrWhiteSpace( value as string );
}
return false;
}
private static bool IsDefaultOrValueTypeDefault( object value )
{
return IsDefault( value, orValueTypeDefault: true );
}
public static AndConstraint<ObjectAssertions> NotBeDefault( this ObjectAssertions assertions, string because = "", params object[] reasonArgs )
{
Execute.Assertion
.BecauseOf( because, reasonArgs )
.ForCondition( !IsDefault( assertions.Subject ) )
.FailWith( "Expected {context:object} to not be default{reason}, but found {0}.", assertions.Subject );
return new AndConstraint<ObjectAssertions>( assertions );
}
public static AndConstraint<StringAssertions> NotBeDefault( this StringAssertions assertions, string because = "", params object[] reasonArgs )
{
Execute.Assertion
.BecauseOf( because, reasonArgs )
.ForCondition( !IsDefault( assertions.Subject ) )
.FailWith( "Expected {context:object} to not be default{reason}, but found {0}.", assertions.Subject );
return new AndConstraint<StringAssertions>( assertions );
}
public static AndConstraint<Numeric.NumericAssertions<T>> NotBeDefault<T>( this Numeric.NumericAssertions<T> assertions, string because = "", params object[] reasonArgs ) where T : struct
{
Execute.Assertion
.BecauseOf( because, reasonArgs )
.ForCondition( !IsDefault( assertions.Subject ) )
.FailWith( "Expected {context:object} to not be default{reason}, but found {0}.", assertions.Subject );
return new AndConstraint<Numeric.NumericAssertions<T>>( assertions );
}
public static AndConstraint<BooleanAssertions> NotBeDefault( this BooleanAssertions assertions, string because = "", params object[] reasonArgs )
{
Execute.Assertion
.BecauseOf( because, reasonArgs )
.ForCondition( !IsDefault( assertions.Subject ) )
.FailWith( "Expected {context:object} to not be default{reason}, but found {0}.", assertions.Subject );
return new AndConstraint<BooleanAssertions>( assertions );
}
public static AndConstraint<GuidAssertions> NotBeDefault( this GuidAssertions assertions, string because = "", params object[] reasonArgs )
{
Execute.Assertion
.BecauseOf( because, reasonArgs )
.ForCondition( !IsDefault( assertions.Subject ) )
.FailWith( "Expected {context:object} to not be default{reason}, but found {0}.", assertions.Subject );
return new AndConstraint<GuidAssertions>( assertions );
}
public static void ShouldNotBeEquivalentToDefault<T>( this T subject, string because = "", params object[] reasonArgs )
{
ShouldNotBeEquivalentToDefault( subject, config => config, because, reasonArgs );
}
public static void ShouldNotBeEquivalentToDefault<T>( this T subject,
Func<EquivalencyAssertionOptions<T>, EquivalencyAssertionOptions<T>> config, string because = "", params object[] reasonArgs )
{
var context = new EquivalencyValidationContext
{
Subject = subject,
Expectation = subject,
CompileTimeType = typeof( T ),
Reason = because,
ReasonArgs = reasonArgs
};
var validator = new EquivalencyValidator(
config( EquivalencyAssertionOptions<T>.Default()
.Using<string>( ctx => ctx.Subject.Should().NotBeDefault() ).WhenTypeIs<string>() )
.WithStrictOrdering()
);
validator.Steps.Remove( validator.Steps.Single( _ => typeof( TryConversionEquivalencyStep ) == _.GetType() ) );
validator.Steps.Remove( validator.Steps.Single( _ => typeof( ReferenceEqualityEquivalencyStep ) == _.GetType() ) );
validator.Steps.Remove( validator.Steps.Single( _ => typeof( SimpleEqualityEquivalencyStep ) == _.GetType() ) );
validator.Steps.Add( new SimpleIsNotDefaultEquivalencyStep() );
validator.AssertEquality( context );
}
}
}
これはそのためのテストです:
[TestMethod]
[TestCategory( TestCategory2 )]
public void Test_NotBeDefault()
{
((Action)(() => ((int?)null).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because null is default for int?" );
((Action)(() => ((int?)0).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because 0 is value type default for int?" );
((Action)(() => 0.Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because null is value type default for int" );
((Action)(() => ((int?)1).Should().NotBeDefault())).ShouldNotThrow( "because 1 is not default for int?" );
((Action)(() => 1.Should().NotBeDefault())).ShouldNotThrow( "because 1 is not default for int" );
((Action)(() => ((object)null).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because null is default for object" );
((Action)(() => ((object)new object()).Should().NotBeDefault())).ShouldNotThrow( "because not null is not default for object" );
((Action)(() => ((string)null).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because null is default for string" );
((Action)(() => ((string)"").Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because empty string is default for string" );
((Action)(() => ((string)"hi").Should().NotBeDefault())).ShouldNotThrow( "because \"hi\" is not default for string" );
((Action)(() => ((bool?)null).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because null is default for bool?" );
((Action)(() => ((bool?)false).Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because false is default for bool?" );
((Action)(() => false.Should().NotBeDefault())).ShouldThrow<AssertFailedException>( "because false is default for bool" );
((Action)(() => ((bool?)true).Should().NotBeDefault())).ShouldNotThrow( "because true is not default for bool?" );
((Action)(() => true.Should().NotBeDefault())).ShouldNotThrow( "because true is not default for bool" );
var actual = new
{
i1 = (int?)null,
i2 = (int?)0,
i3 = 0,
i4 = (int?)1,
i5 = 1,
s1 = (string)null,
s2 = (string)"",
s3 = (string)"hi",
b1 = (bool?)null,
b2 = (bool?)false,
b3 = false,
b4 = (bool?)true,
b5 = true,
n1 = (PlainClass)null,
n2 = new PlainClass(),
n3 = new PlainClass
{
Key = 10,
NestedProperty = new object()
},
a1 = (PlainClass[])null,
a2 = new [] { "", "hi", null },
a3 = new [] { 0, 11 },
a4 = new [] { new PlainClass { Key = 42 } },
g1 = (Guid?)null,
g2 = (Guid)Guid.Empty,
g3 = Guid.NewGuid()
};
((Action)(() => actual.ShouldNotBeEquivalentToDefault())).ShouldThrow<AssertFailedException>().WithMessage(
@"Expected property i1 to not be default, but found <null>.
Expected property i2 to not be default, but found 0.
Expected property i3 to not be default, but found 0.
Expected property s1 to not be default, but found <null>.
Expected property s2 to not be default, but found """".
Expected property b1 to not be default, but found <null>.
Expected property b2 to not be default, but found False.
Expected property b3 to not be default, but found False.
Expected property n1 to not be default, but found <null>.
Expected property n2.Key to not be default, but found 0.
Expected property n2.NestedProperty to not be default, but found <null>.
Expected property a1 to not be default, but found <null>.
Expected property a2[0] to not be default, but found """".
Expected property a2[2] to not be default, but found <null>.
Expected property a3[0] to not be default, but found 0.
Expected property a4[0].NestedProperty to not be default, but found <null>.
Expected property g1 to not be default, but found <null>.
Expected property g2 to not be default, but found {00000000-0000-0000-0000-000000000000}.
With configuration:
- Select all declared properties
- Match property by name (or throw)
- Invoke Action<String> when info.RuntimeType.IsSameOrInherits(System.String)
- Invoke Action<String> when info.RuntimeType.IsSameOrInherits(System.String)
" );
}