diff --git a/Source/aweXpect.Core/Core/ExpectationBuilder.cs b/Source/aweXpect.Core/Core/ExpectationBuilder.cs index 1c2ba5fa9..701876289 100644 --- a/Source/aweXpect.Core/Core/ExpectationBuilder.cs +++ b/Source/aweXpect.Core/Core/ExpectationBuilder.cs @@ -203,7 +203,7 @@ public MemberExpectationBuilder ForMember( } Node root = _node; - _node = _node.AddMapping(memberAccessor, expectationTextGenerator) ?? _node; + _node = _node.AddMapping(memberAccessor, expectationTextGenerator); if (replaceIt) { _it = memberAccessor.ToString().Trim(); @@ -247,7 +247,7 @@ public MemberExpectationBuilder ForAsyncMember( ExpectationBuilder? inner, ExpectationGrammars grammars = ExpectationGrammars.None) - : ExpectationBuilder("", grammars), IEqualityComparer> + : ExpectationBuilder("", grammars), + IEqualityComparer> { /// public bool Equals(ManualExpectationBuilder? x, ManualExpectationBuilder? y) diff --git a/Source/aweXpect.Core/Core/MemberAccessor.cs b/Source/aweXpect.Core/Core/MemberAccessor.cs index 308145514..cbf6885e7 100644 --- a/Source/aweXpect.Core/Core/MemberAccessor.cs +++ b/Source/aweXpect.Core/Core/MemberAccessor.cs @@ -67,8 +67,7 @@ public static MemberAccessor FromFuncAsMemberAccessor( public override bool Equals(object? obj) => obj is MemberAccessor other && Equals(other); private bool Equals(MemberAccessor other) - => ToString().Equals(other.ToString()) && - _accessor.ToString()?.Equals(other._accessor.ToString()) == true; + => ToString().Equals(other.ToString()); /// public override int GetHashCode() => ToString().GetHashCode(); diff --git a/Source/aweXpect.Core/Core/Nodes/AndNode.cs b/Source/aweXpect.Core/Core/Nodes/AndNode.cs index 0f215cebd..37a5a0f90 100644 --- a/Source/aweXpect.Core/Core/Nodes/AndNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/AndNode.cs @@ -29,14 +29,14 @@ public override void AddConstraint(IConstraint constraint) => Current.AddConstraint(constraint); /// - public override Node? AddMapping(MemberAccessor memberAccessor, + public override Node AddMapping(MemberAccessor memberAccessor, Action? expectationTextGenerator = null) where TValue : default where TTarget : default => Current.AddMapping(memberAccessor, expectationTextGenerator); /// - public override Node? AddAsyncMapping( + public override Node AddAsyncMapping( MemberAccessor> memberAccessor, Action? expectationTextGenerator = null) where TValue : default diff --git a/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs b/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs index 1134435f7..bebc0467e 100644 --- a/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs @@ -37,7 +37,7 @@ public override void AddConstraint(IConstraint constraint) } /// - public override Node? AddMapping(MemberAccessor memberAccessor, + public override Node AddMapping(MemberAccessor memberAccessor, Action? expectationTextGenerator = null) where TValue : default where TTarget : default @@ -50,7 +50,7 @@ public override void AddConstraint(IConstraint constraint) } /// - public override Node? AddAsyncMapping( + public override Node AddAsyncMapping( MemberAccessor> memberAccessor, Action? expectationTextGenerator = null) where TValue : default diff --git a/Source/aweXpect.Core/Core/Nodes/Node.cs b/Source/aweXpect.Core/Core/Nodes/Node.cs index feb70875c..76a3fe7b1 100644 --- a/Source/aweXpect.Core/Core/Nodes/Node.cs +++ b/Source/aweXpect.Core/Core/Nodes/Node.cs @@ -20,14 +20,14 @@ internal abstract class Node /// Add a mapping constraint which maps the value according to the to a member /// and applies this value to the inner expectations. /// - public abstract Node? AddMapping(MemberAccessor memberAccessor, + public abstract Node AddMapping(MemberAccessor memberAccessor, Action? expectationTextGenerator = null); /// /// Add a mapping constraint which maps the value according to the asynchronously /// to a member and applies this value to the inner expectations. /// - public abstract Node? AddAsyncMapping( + public abstract Node AddAsyncMapping( MemberAccessor> memberAccessor, Action? expectationTextGenerator = null); diff --git a/Source/aweXpect.Core/Core/Nodes/OrNode.cs b/Source/aweXpect.Core/Core/Nodes/OrNode.cs index 799f53df9..b43f6307d 100644 --- a/Source/aweXpect.Core/Core/Nodes/OrNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/OrNode.cs @@ -29,14 +29,14 @@ public override void AddConstraint(IConstraint constraint) => Current.AddConstraint(constraint); /// - public override Node? AddMapping(MemberAccessor memberAccessor, + public override Node AddMapping(MemberAccessor memberAccessor, Action? expectationTextGenerator = null) where TValue : default where TTarget : default => Current.AddMapping(memberAccessor, expectationTextGenerator); /// - public override Node? AddAsyncMapping( + public override Node AddAsyncMapping( MemberAccessor> memberAccessor, Action? expectationTextGenerator = null) where TValue : default diff --git a/Source/aweXpect.Core/Core/Nodes/WhichNode.cs b/Source/aweXpect.Core/Core/Nodes/WhichNode.cs index a44834fa1..f697083a5 100644 --- a/Source/aweXpect.Core/Core/Nodes/WhichNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/WhichNode.cs @@ -43,19 +43,19 @@ public override void AddConstraint(IConstraint constraint) => _inner?.AddConstraint(constraint); /// - public override Node? AddMapping(MemberAccessor memberAccessor, + public override Node AddMapping(MemberAccessor memberAccessor, Action? expectationTextGenerator = null) where TValue : default where TTarget : default - => _inner?.AddMapping(memberAccessor, expectationTextGenerator); + => _inner?.AddMapping(memberAccessor, expectationTextGenerator) ?? this; /// - public override Node? AddAsyncMapping( + public override Node AddAsyncMapping( MemberAccessor> memberAccessor, Action? expectationTextGenerator = null) where TValue : default where TTarget : default - => _inner?.AddAsyncMapping(memberAccessor, expectationTextGenerator); + => _inner?.AddAsyncMapping(memberAccessor, expectationTextGenerator) ?? this; /// public override void AddNode(Node node, string? separator = null) diff --git a/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs b/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs index 60891ac66..854a51bfa 100644 --- a/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs @@ -6,6 +6,26 @@ namespace aweXpect.Core.Tests.Core; public class ExpectationBuilderTests { + [Fact] + public async Task ForAsyncMember_ShouldUseAndResetExpectationGrammars() + { + ManualExpectationBuilder sut = new(null); + ExpectationGrammars usedExpectationGrammars = ExpectationGrammars.None; + + sut.ForAsyncMember(MemberAccessor>.FromFunc(x => Task.FromResult(x.Length), "length ")) + .AddExpectations(expectationBuilder => expectationBuilder.AddConstraint((_, g) + => + { + usedExpectationGrammars = g; + return new DummyConstraint(v => v == 2, "equal to 2"); + }), _ => ExpectationGrammars.Nested); + + await sut.IsMetBy("bar", null!, CancellationToken.None); + + await That(usedExpectationGrammars).IsEqualTo(ExpectationGrammars.Nested); + await That(sut.ExpectationGrammars).IsEqualTo(ExpectationGrammars.None); + } + [Fact] public async Task ForAsyncMember_WithFailingExpectation_ShouldReturnFailureConstraintResult() { @@ -36,6 +56,42 @@ public async Task ForAsyncMember_WithSucceedingExpectation_ShouldReturnSuccessCo await That(constraintResult.GetExpectationText()).IsEqualTo("length equal to 3"); } + [Fact] + public async Task ForAsyncMember_WithValidation_ShouldIncludeValidation() + { + ManualExpectationBuilder sut = new(null); + + sut.ForAsyncMember(MemberAccessor>.FromFunc(x => Task.FromResult(x.Length), "length ")) + .Validate((_, _) => new DummyConstraint(_ => false, "validated and ")) + .AddExpectations(expectationBuilder => expectationBuilder.AddConstraint((_, _) + => new DummyConstraint(v => v == 3, "equal to 3"))); + + ConstraintResult constraintResult = await sut.IsMetBy("bar", null!, CancellationToken.None); + + await That(constraintResult.Outcome).IsEqualTo(Outcome.Failure); + await That(constraintResult.GetExpectationText()).IsEqualTo("validated and length equal to 3"); + } + + [Fact] + public async Task ForMember_ShouldUseAndResetExpectationGrammars() + { + ManualExpectationBuilder sut = new(null); + ExpectationGrammars usedExpectationGrammars = ExpectationGrammars.None; + + sut.ForMember(MemberAccessor.FromFunc(x => x.Length, "length ")) + .AddExpectations(expectationBuilder => expectationBuilder.AddConstraint((_, g) + => + { + usedExpectationGrammars = g; + return new DummyConstraint(v => v == 2, "equal to 2"); + }), _ => ExpectationGrammars.Nested); + + await sut.IsMetBy("bar", null!, CancellationToken.None); + + await That(usedExpectationGrammars).IsEqualTo(ExpectationGrammars.Nested); + await That(sut.ExpectationGrammars).IsEqualTo(ExpectationGrammars.None); + } + [Fact] public async Task ForMember_WithFailingExpectation_ShouldReturnFailureConstraintResult() { @@ -66,6 +122,22 @@ public async Task ForMember_WithSucceedingExpectation_ShouldReturnSuccessConstra await That(constraintResult.GetExpectationText()).IsEqualTo("length equal to 3"); } + [Fact] + public async Task ForMember_WithValidation_ShouldIncludeValidation() + { + ManualExpectationBuilder sut = new(null); + + sut.ForMember(MemberAccessor.FromFunc(x => x.Length, "length ")) + .Validate((_, _) => new DummyConstraint(_ => false, "validated and ")) + .AddExpectations(expectationBuilder => expectationBuilder.AddConstraint((_, _) + => new DummyConstraint(v => v == 3, "equal to 3"))); + + ConstraintResult constraintResult = await sut.IsMetBy("bar", null!, CancellationToken.None); + + await That(constraintResult.Outcome).IsEqualTo(Outcome.Failure); + await That(constraintResult.GetExpectationText()).IsEqualTo("validated and length equal to 3"); + } + [Fact] public async Task WhenSubjectHasMultipleLines_ShouldTrimCommonWhiteSpace() { diff --git a/Tests/aweXpect.Core.Tests/Core/ExpectationGrammarsExtensionsTests.cs b/Tests/aweXpect.Core.Tests/Core/ExpectationGrammarsExtensionsTests.cs new file mode 100644 index 000000000..92d082226 --- /dev/null +++ b/Tests/aweXpect.Core.Tests/Core/ExpectationGrammarsExtensionsTests.cs @@ -0,0 +1,55 @@ +namespace aweXpect.Core.Tests.Core; + +public sealed class ExpectationGrammarsExtensionsTests +{ + [Theory] + [InlineData(ExpectationGrammars.None, false)] + [InlineData(ExpectationGrammars.Negated, true)] + [InlineData(ExpectationGrammars.Plural | ExpectationGrammars.Negated, true)] + [InlineData(ExpectationGrammars.Nested | ExpectationGrammars.Plural, false)] + [InlineData(ExpectationGrammars.Nested | ExpectationGrammars.Plural | ExpectationGrammars.Negated, true)] + public async Task IsNegated_ShouldReturnExpectedValue(ExpectationGrammars input, bool expected) + { + bool result = input.IsNegated(); + + await That(result).IsEqualTo(expected); + } + + [Theory] + [InlineData(ExpectationGrammars.None, false)] + [InlineData(ExpectationGrammars.Nested, true)] + [InlineData(ExpectationGrammars.Plural | ExpectationGrammars.Nested, true)] + [InlineData(ExpectationGrammars.Negated | ExpectationGrammars.Plural, false)] + [InlineData(ExpectationGrammars.Nested | ExpectationGrammars.Plural | ExpectationGrammars.Negated, true)] + public async Task IsNested_ShouldReturnExpectedValue(ExpectationGrammars input, bool expected) + { + bool result = input.IsNested(); + + await That(result).IsEqualTo(expected); + } + + [Theory] + [InlineData(ExpectationGrammars.None, false)] + [InlineData(ExpectationGrammars.Plural, true)] + [InlineData(ExpectationGrammars.Plural | ExpectationGrammars.Nested, true)] + [InlineData(ExpectationGrammars.Negated | ExpectationGrammars.Nested, false)] + [InlineData(ExpectationGrammars.Nested | ExpectationGrammars.Plural | ExpectationGrammars.Negated, true)] + public async Task IsPlural_ShouldReturnExpectedValue(ExpectationGrammars input, bool expected) + { + bool result = input.IsPlural(); + + await That(result).IsEqualTo(expected); + } + + [Theory] + [InlineData(ExpectationGrammars.None, ExpectationGrammars.Negated)] + [InlineData(ExpectationGrammars.Plural, ExpectationGrammars.Plural | ExpectationGrammars.Negated)] + [InlineData(ExpectationGrammars.Nested | ExpectationGrammars.Plural, + ExpectationGrammars.Nested | ExpectationGrammars.Plural | ExpectationGrammars.Negated)] + public async Task Negate_ShouldAddNegated(ExpectationGrammars input, ExpectationGrammars expected) + { + ExpectationGrammars result = input.Negate(); + + await That(result).IsEqualTo(expected); + } +} diff --git a/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs b/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs index c014d2638..f8609559b 100644 --- a/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs @@ -113,6 +113,28 @@ public async Task Equals_WithSelf_ShouldBeTrue() await That(result).IsTrue(); } + [Fact] + public async Task GetHashCode_DifferentConstraint_ShouldNotBeEqual() + { + ManualExpectationBuilder sut1 = new(null); + sut1.AddConstraint((_, _, _) => new DummyConstraint("foo")); + ManualExpectationBuilder sut2 = new(null); + sut2.ForWhich(x => x) + .AddConstraint((_, _, _) => new DummyConstraint("foo")); + + await That(sut1.GetHashCode()).IsNotEqualTo(sut2.GetHashCode()); + } + + [Fact] + public async Task GetHashCode_SameConstraint_ShouldBeEqual() + { + ManualExpectationBuilder sut1 = new(null); + sut1.AddConstraint((_, _, _) => new DummyConstraint("foo")); + ManualExpectationBuilder sut2 = new(null); + sut2.AddConstraint((_, _, _) => new DummyConstraint("foo")); + + await That(sut1.GetHashCode()).IsEqualTo(sut2.GetHashCode()); + } [Fact] public async Task IsMet_ShouldThrowNotSupportedException() diff --git a/Tests/aweXpect.Core.Tests/Core/MemberAccessorTests.cs b/Tests/aweXpect.Core.Tests/Core/MemberAccessorTests.cs index 81cf44ce7..b0428e0cb 100644 --- a/Tests/aweXpect.Core.Tests/Core/MemberAccessorTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/MemberAccessorTests.cs @@ -2,6 +2,50 @@ public sealed class MemberAccessorTests { + [Theory] + [InlineData(".Length ", true)] + [InlineData(".SomethingElse ", false)] + public async Task Equals_ShouldCompareStringRepresentation(string otherStringRepresentation, bool expectedResult) + { + MemberAccessor sut = MemberAccessor.FromExpression(x => x.Length); + MemberAccessor other = + MemberAccessor.FromFunc(x => x.Length, otherStringRepresentation); + + bool result = sut.Equals(other); + + await That(result).IsEqualTo(expectedResult); + } + + [Fact] + public async Task Equals_ToNull_ShouldBeFalse() + { + MemberAccessor sut = MemberAccessor.FromExpression(x => x.Length); + + bool result = sut.Equals(null); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_ToOtherObject_ShouldBeFalse() + { + MemberAccessor sut = MemberAccessor.FromExpression(x => x.Length); + object other = MemberAccessor.FromExpression(x => x); + + bool result = sut.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task FromExpression_ShouldCompileExpression() + { + MemberAccessor subject = MemberAccessor + .FromExpression(x => x.Length); + + await That(subject.AccessMember("foo")).IsEqualTo(3); + } + [Fact] public async Task FromExpression_ShouldGetMemberPath() { @@ -36,4 +80,22 @@ public async Task FromFuncAsMemberAccessor_ShouldTryToExtractMemberAccessor(stri await That(subject.ToString()).IsEqualTo(expected); } + + [Fact] + public async Task GetHashCode_DifferentConstraint_ShouldNotBeEqual() + { + MemberAccessor sut1 = MemberAccessor.FromFunc(x => x.Length, "foo"); + MemberAccessor sut2 = MemberAccessor.FromFunc(x => x.Length, "bar"); + + await That(sut1.GetHashCode()).IsNotEqualTo(sut2.GetHashCode()); + } + + [Fact] + public async Task GetHashCode_SameConstraint_ShouldBeEqual() + { + MemberAccessor sut1 = MemberAccessor.FromFunc(x => x.Length, "foo"); + MemberAccessor sut2 = MemberAccessor.FromFunc(x => x.Length, "foo"); + + await That(sut1.GetHashCode()).IsEqualTo(sut2.GetHashCode()); + } }