From ad3261441db016b115b15c79eaa32755e5dab94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Fri, 5 Sep 2025 08:41:48 +0200 Subject: [PATCH 1/2] Add missing tests --- .../Adapters/TestFrameworkAdapterTests.cs | 10 +++++++++ ...onstraintResultTests.FromExceptionTests.cs | 18 ++++++++++++++- .../Core/ManualExpectationBuilderTests.cs | 22 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Tests/aweXpect.Core.Tests/Core/Adapters/TestFrameworkAdapterTests.cs b/Tests/aweXpect.Core.Tests/Core/Adapters/TestFrameworkAdapterTests.cs index d2a218bd5..8a15a0cb3 100644 --- a/Tests/aweXpect.Core.Tests/Core/Adapters/TestFrameworkAdapterTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Adapters/TestFrameworkAdapterTests.cs @@ -27,6 +27,16 @@ public async Task FromType_WhenNameDoesNotExist_ShouldReturnNull() await That(exception).IsNull(); } + [Fact] + public async Task Inconclusive_MissingAssemblyName_ShouldThrowNotSupportedException() + { + MyTestFrameworkAdapter adapter = new(MissingAssembly, skipException: new MyException()); + _ = adapter.IsAvailable; + + await That(() => adapter.Inconclusive("foo")).Throws() + .WithMessage("Failed to create the inconclusive assertion type"); + } + [Fact] public async Task Inconclusive_ValidAssemblyName_ShouldThrowNotSupportedException() { diff --git a/Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultTests.FromExceptionTests.cs b/Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultTests.FromExceptionTests.cs index b7cc0182e..585672bc9 100644 --- a/Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultTests.FromExceptionTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultTests.FromExceptionTests.cs @@ -50,6 +50,23 @@ public async Task AppendResult_SpecificException_ShouldAppendExpectedValue() await That(sb.ToString()).IsEqualTo("it did throw an ArgumentException"); } + [Theory] + [InlineData(Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Undecided)] + public async Task Negate_ShouldNegateInnerOutcome(Outcome innerOutcome, Outcome expectedAfterNegation) + { + DummyConstraintResult inner = new(innerOutcome, "foo"); + Exception exception = new("bar"); + DummyExpectationBuilder expectationBuilder = new(); + MyFromExceptionConstraintResult sut = new(inner, exception, expectationBuilder); + + sut.Negate(); + + await That(sut.Outcome).IsEqualTo(Outcome.Failure); + await That(inner.Outcome).IsEqualTo(expectedAfterNegation); + } + [Fact] public async Task Outcome_ShouldBeFailure() { @@ -61,7 +78,6 @@ public async Task Outcome_ShouldBeFailure() await That(sut.Outcome).IsEqualTo(Outcome.Failure); } - [Fact] public async Task SetOutcome_ShouldBeForwardedToInner() { diff --git a/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs b/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs index f8609559b..181f72eaa 100644 --- a/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/ManualExpectationBuilderTests.cs @@ -93,6 +93,16 @@ public async Task Equals_FirstNull_ShouldBeFalse() await That(result).IsFalse(); } + [Fact] + public async Task Equals_ObjectNull_ShouldBeFalse() + { + ManualExpectationBuilder sut = new(null); + + bool result = sut.Equals(null); + + await That(result).IsFalse(); + } + [Fact] public async Task Equals_SecondNull_ShouldBeFalse() { @@ -136,6 +146,18 @@ public async Task GetHashCode_SameConstraint_ShouldBeEqual() await That(sut1.GetHashCode()).IsEqualTo(sut2.GetHashCode()); } + [Fact] + public async Task GetHashCode_WithParameter_ShouldUseHashCodeFromParameter() + { + 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()).IsEqualTo(sut2.GetHashCode(sut1)); + } + [Fact] public async Task IsMet_ShouldThrowNotSupportedException() { From 0afe4d937b87d375bb0b44992139593173ce697d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Fri, 5 Sep 2025 09:30:03 +0200 Subject: [PATCH 2/2] fix: Outcome of `OrConstraintResult` --- Source/aweXpect.Core/Core/Nodes/AndNode.cs | 15 +- .../Core/Nodes/ExpectationNode.cs | 6 +- Source/aweXpect.Core/Core/Nodes/OrNode.cs | 21 +- .../Core/Nodes/AndNodeTests.cs | 248 +++++++++++++ .../Core/Nodes/AsyncMappingNodeTests.cs | 52 +++ .../Core/Nodes/ExpectationNodeTests.cs | 331 +++++++++++++++++- .../Core/Nodes/MappingNodeTests.cs | 49 +++ .../Core/Nodes/OrNodeTests.cs | 254 +++++++++++++- .../Core/Nodes/WhichNodeTests.cs | 10 +- .../TestHelpers/DummyConstraintResult.cs | 17 + .../TestHelpers/DummyNode.cs | 14 +- 11 files changed, 999 insertions(+), 18 deletions(-) diff --git a/Source/aweXpect.Core/Core/Nodes/AndNode.cs b/Source/aweXpect.Core/Core/Nodes/AndNode.cs index 37a5a0f90..93d06cf30 100644 --- a/Source/aweXpect.Core/Core/Nodes/AndNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/AndNode.cs @@ -108,7 +108,20 @@ public override void AppendExpectation(StringBuilder stringBuilder, string? inde private bool Equals(AndNode other) => Current.Equals(other.Current) && _nodes.SequenceEqual(other._nodes); /// - public override int GetHashCode() => Current.GetHashCode() ^ _nodes.GetHashCode(); + public override int GetHashCode() + { + unchecked + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + int hash = 19 * Current.GetHashCode(); + foreach (Node node in _nodes.Select(x => x.Item2)) + { + hash = (hash * 31) + node.GetHashCode(); + } + + return hash; + } + } private static ConstraintResult CombineResults( ConstraintResult? combinedResult, diff --git a/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs b/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs index bebc0467e..55efe34ae 100644 --- a/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/ExpectationNode.cs @@ -155,5 +155,9 @@ private bool Equals(ExpectationNode other) } /// - public override int GetHashCode() => GetType().GetHashCode(); + // ReSharper disable NonReadonlyMemberInGetHashCode + public override int GetHashCode() + => _constraint?.GetType().GetHashCode() ?? 17 + + _inner?.GetHashCode() ?? 0; + // ReSharper restore NonReadonlyMemberInGetHashCode } diff --git a/Source/aweXpect.Core/Core/Nodes/OrNode.cs b/Source/aweXpect.Core/Core/Nodes/OrNode.cs index b43f6307d..a082dcce5 100644 --- a/Source/aweXpect.Core/Core/Nodes/OrNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/OrNode.cs @@ -102,7 +102,20 @@ public override void AppendExpectation(StringBuilder stringBuilder, string? inde private bool Equals(OrNode other) => Current.Equals(other.Current) && _nodes.SequenceEqual(other._nodes); /// - public override int GetHashCode() => Current.GetHashCode() ^ _nodes.GetHashCode(); + public override int GetHashCode() + { + unchecked + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + int hash = 19 * Current.GetHashCode(); + foreach (Node node in _nodes.Select(x => x.Item2)) + { + hash = (hash * 31) + node.GetHashCode(); + } + + return hash; + } + } private static ConstraintResult CombineResults( ConstraintResult? combinedResult, @@ -143,9 +156,9 @@ private static Outcome Or(Outcome left, Outcome right) => (left, right) switch { (Outcome.Failure, Outcome.Failure) => Outcome.Failure, - (_, Outcome.Undecided) => Outcome.Undecided, - (Outcome.Undecided, _) => Outcome.Undecided, - (_, _) => Outcome.Success, + (_, Outcome.Success) => Outcome.Success, + (Outcome.Success, _) => Outcome.Success, + (_, _) => Outcome.Undecided, }; public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/AndNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/AndNodeTests.cs index 2ccb62442..846a3e3d2 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/AndNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/AndNodeTests.cs @@ -1,6 +1,7 @@ using System.Text; using System.Threading; using aweXpect.Core.Constraints; +using aweXpect.Core.Helpers; using aweXpect.Core.Nodes; using aweXpect.Core.Tests.TestHelpers; @@ -8,6 +9,81 @@ namespace aweXpect.Core.Tests.Core.Nodes; public sealed class AndNodeTests { + [Fact] + public async Task AddAsyncMapping_ShouldUseCurrentNode() + { + MemberAccessor> memberAccessor = + MemberAccessor>.FromExpression(x => Task.FromResult(x.Length)); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + AndNode node = new(first); + + node.AddAsyncMapping(memberAccessor); + node.AddNode(second); + + await That(first.MappingMemberAccessor).IsSameAs(memberAccessor); + await That(second.MappingMemberAccessor).IsNull(); + } + + [Fact] + public async Task AddAsyncMapping_ShouldUseSecondNode() + { + MemberAccessor> memberAccessor = + MemberAccessor>.FromExpression(x => Task.FromResult(x.Length)); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + AndNode node = new(first); + node.AddNode(second); + + node.AddAsyncMapping(memberAccessor); + + await That(first.MappingMemberAccessor).IsNull(); + await That(second.MappingMemberAccessor).IsSameAs(memberAccessor); + } + + [Fact] + public async Task AddMapping_ShouldUseCurrentNode() + { + MemberAccessor memberAccessor = MemberAccessor.FromExpression(x => x.Length); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + AndNode node = new(first); + + node.AddMapping(memberAccessor); + node.AddNode(second); + + await That(first.MappingMemberAccessor).IsSameAs(memberAccessor); + await That(second.MappingMemberAccessor).IsNull(); + } + + [Fact] + public async Task AddMapping_ShouldUseSecondNode() + { + MemberAccessor memberAccessor = MemberAccessor.FromExpression(x => x.Length); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + AndNode node = new(first); + node.AddNode(second); + + node.AddMapping(memberAccessor); + + await That(first.MappingMemberAccessor).IsNull(); + await That(second.MappingMemberAccessor).IsSameAs(memberAccessor); + } + + [Fact] + public async Task AppendExpectation_WithAdditionalNodes_ShouldUseAllNodes() + { + AndNode node = new(new DummyNode("foo")); + node.AddNode(new DummyNode("bar")); + node.AddNode(new DummyNode("baz")); + StringBuilder sb = new(); + + node.AppendExpectation(sb); + + await That(sb.ToString()).IsEqualTo("foo and bar and baz"); + } + [Fact] public async Task AppendExpectation_WithoutAdditionalNodes_ShouldUseFirstNode() { @@ -19,6 +95,134 @@ public async Task AppendExpectation_WithoutAdditionalNodes_ShouldUseFirstNode() await That(sb.ToString()).IsEqualTo("foo"); } + [Fact] + public async Task Equals_IfCurrentNodeIsDifferent_ShouldBeFalse() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + AndNode node1 = new(innerNode1); + AndNode node2 = new(innerNode2); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfCurrentNodeIsTheSame_ShouldBeTrue() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + AndNode node1 = new(innerNode1); + AndNode node2 = new(innerNode2); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreDifferent_ShouldBeFalse() + { + DummyNode innerNode0 = new("0", () => new DummyConstraintResult(Outcome.Success, "0", "")); + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode currentNode = new("3", () => new DummyConstraintResult(Outcome.Success, "3", "")); + AndNode node1 = new(innerNode0); + node1.AddNode(innerNode1); + node1.AddNode(currentNode); + AndNode node2 = new(innerNode0); + node2.AddNode(innerNode2); + node2.AddNode(currentNode); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreSame_ShouldBeTrue() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode3 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode innerNode4 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode currentNode = new("3", () => new DummyConstraintResult(Outcome.Success, "3", "")); + AndNode node1 = new(innerNode1); + node1.AddNode(innerNode3); + node1.AddNode(currentNode); + AndNode node2 = new(innerNode2); + node2.AddNode(innerNode4); + node2.AddNode(currentNode); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_WhenOtherIsDifferentNode_ShouldBeFalse() + { + DummyNode inner = new("foo"); + AndNode node = new(inner); + object other = new OrNode(inner); + + bool result = node.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_WhenOtherIsNull_ShouldBeFalse() + { + AndNode node = new(new DummyNode("foo")); + + bool result = node.Equals(null); + + await That(result).IsFalse(); + } + + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Success)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task NegatedOutcome_ShouldBeExpected(Outcome node1, Outcome node2, Outcome expectedOutcome) + { + AndNode node = new(new DummyNode("", () => new DummyConstraintResult(node1))); + node.AddNode(new DummyNode("", () => new DummyConstraintResult(node2))); + + ConstraintResult result = await node.IsMetBy(0, null!, CancellationToken.None); + result.Negate(); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + } + + [Fact] + public async Task NegatedResult_ShouldUseOrAsSeparator() + { + AndNode node = new(new DummyNode("", () => new DummyConstraintResult(Outcome.Success, "foo"))); + node.AddNode(new DummyNode("", () => new DummyConstraintResult(Outcome.Success, "bar"))); + StringBuilder sb1 = new(); + StringBuilder sb2 = new(); + + ConstraintResult result = await node.IsMetBy(0, null!, CancellationToken.None); + result.AppendExpectation(sb1); + + result.Negate(); + + result.AppendExpectation(sb2); + await That(sb1.ToString()).IsEqualTo("foo and bar"); + await That(sb2.ToString()).IsEqualTo("foo or bar"); + } + [Theory] [InlineData(Outcome.Success, Outcome.Success, Outcome.Success)] [InlineData(Outcome.Failure, Outcome.Success, Outcome.Failure)] @@ -37,6 +241,50 @@ public async Task Outcome_ShouldBeExpected(Outcome node1, Outcome node2, Outcome await That(result.Outcome).IsEqualTo(expectedOutcome); } + [Fact] + public async Task SetReason_WithAdditionalNodes_ShouldUseCurrentNode() + { + DummyNode node1 = new("node1"); + DummyNode node2 = new("node2"); + DummyNode current = new("current"); + AndNode node = new(node1); + node.AddNode(node2); + node.AddNode(current); + + node.SetReason(new BecauseReason("bar")); + + await That(current.ReceivedReason).IsEqualTo(", because bar"); + await That(node1.ReceivedReason).IsNull(); + await That(node2.ReceivedReason).IsNull(); + } + + [Fact] + public async Task SetReason_WithAdditionalNodes_WhenCurrentNodeIsEmptyExpectationNode_ShouldUseLastNode() + { + DummyNode node1 = new("node1"); + DummyNode node2 = new("node2"); + ExpectationNode current = new(); + AndNode node = new(node1); + node.AddNode(node2); + node.AddNode(current); + + node.SetReason(new BecauseReason("bar")); + + await That(node1.ReceivedReason).IsNull(); + await That(node2.ReceivedReason).IsEqualTo(", because bar"); + } + + [Fact] + public async Task SetReason_WithoutAdditionalNodes_ShouldSetReasonForCurrentNode() + { + DummyNode current = new("current"); + AndNode node = new(current); + + node.SetReason(new BecauseReason("bar")); + + await That(current.ReceivedReason).IsEqualTo(", because bar"); + } + [Fact] public async Task ShouldConsiderFurtherProcessingStrategy() { diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/AsyncMappingNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/AsyncMappingNodeTests.cs index a802a9747..6f890af80 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/AsyncMappingNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/AsyncMappingNodeTests.cs @@ -10,6 +10,58 @@ namespace aweXpect.Core.Tests.Core.Nodes; public class AsyncMappingNodeTests { + [Fact] + public async Task Equals_IfMemberAccessorsAreDifferent_ShouldBeFalse() + { + AsyncMappingNode node1 = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length1 ")); + AsyncMappingNode node2 = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length2 ")); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfMemberAccessorsAreSame_ShouldBeTrue() + { + AsyncMappingNode node1 = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length ")); + AsyncMappingNode node2 = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length ")); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_WhenOtherIsDifferentNode_ShouldBeFalse() + { + AsyncMappingNode node = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length ")); + object other = new AsyncMappingNode( + MemberAccessor>.FromFunc(s => Task.FromResult(s * 2), " duplicate ")); + + bool result = node.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_WhenOtherIsNull_ShouldBeFalse() + { + AsyncMappingNode node = new( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length ")); + + bool result = node.Equals(null); + + await That(result).IsFalse(); + } + [Fact] public async Task IsMetBy_ShouldUseInnerConstraintWithOuterValue() { diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/ExpectationNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/ExpectationNodeTests.cs index e5e333e7b..3403d679d 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/ExpectationNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/ExpectationNodeTests.cs @@ -9,6 +9,128 @@ namespace aweXpect.Core.Tests.Core.Nodes; public class ExpectationNodeTests { + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Success)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task AddAsyncMapping_NegatedResult_ShouldHaveExpectedOutcome( + Outcome node1, Outcome node2, Outcome expectedOutcome) + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node1, "foo1", "bar1"))); + node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length: ")) + .AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node2, "foo2", "bar2"))); + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + result.Negate(); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + } + + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task AddAsyncMapping_ShouldUseAndCombination(Outcome node1, Outcome node2, Outcome expectedOutcome) + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node1, "foo1", "bar1"))); + node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length: ")) + .AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node2, "foo2", "bar2"))); + StringBuilder expectationSb = new(); + StringBuilder resultSb = new(); + string expectedResult = (node1 == Outcome.Failure, node2 == Outcome.Failure) switch + { + (true, true) => "bar1 and bar2", + (true, _) => "bar1", + (_, true) => "bar2", + (_, _) => "", + }; + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + result.AppendExpectation(expectationSb); + result.AppendResult(resultSb); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + await That(expectationSb.ToString()).IsEqualTo("foo1 length: foo2"); + await That(resultSb.ToString()).IsEqualTo(expectedResult); + } + + [Fact] + public async Task AddAsyncMapping_TryGetValue_ShouldGetValueFromLeftNode() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult("foo", Outcome.Undecided, "foo1", "bar1"))); + node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(1, Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out string? value); + + await That(result).IsTrue(); + await That(value).IsEqualTo("foo"); + } + + [Fact] + public async Task AddAsyncMapping_TryGetValue_ShouldGetValueFromRightNode() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(Outcome.Undecided, "foo1", "bar1"))); + node.AddAsyncMapping( + MemberAccessor>.FromFunc(s => Task.FromResult(s.Substring(1)), " substring: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out string? value); + + await That(result).IsTrue(); + await That(value).IsEqualTo("foobar"); + } + + [Fact] + public async Task AddAsyncMapping_TryGetValue_WhenTypeDoesNotMatchAnyNode_ShouldReturnFalse() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult("foo", Outcome.Undecided, "foo1", "bar1"))); + node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(42, Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out DateTime? value); + + await That(result).IsFalse(); + await That(value).IsNull(); + } + + [Fact] + public async Task AddAsyncMapping_WithCustomExpectationTextGenerator_ShouldUseIt() + { + ExpectationNode node = new(); + node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s.Length), " length: "), + (m, sb) => sb.Append("my custom generator:").Append(m)) + .AddConstraint(new DummyConstraint(_ => true, "yeah")); + StringBuilder sb = new(); + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + + result.AppendExpectation(sb); + await That(sb.ToString()).IsEqualTo("my custom generator: length: yeah"); + } + [Fact] public async Task AddConstraint_Twice_ShouldThrowInvalidOperationException() { @@ -52,6 +174,127 @@ public async Task AddConstraint_WithMapping_ShouldForwardToInnerNode() await That(sb.ToString()).IsEqualTo("foobar"); } + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Success)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task AddMapping_NegatedResult_ShouldHaveExpectedOutcome( + Outcome node1, Outcome node2, Outcome expectedOutcome) + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node1, "foo1", "bar1"))); + node.AddMapping(MemberAccessor.FromFunc(s => s.Length, " length: ")) + .AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node2, "foo2", "bar2"))); + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + result.Negate(); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + } + + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Success)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task AddMapping_ShouldUseAndCombination(Outcome node1, Outcome node2, Outcome expectedOutcome) + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node1, "foo1", "bar1"))); + node.AddMapping(MemberAccessor.FromFunc(s => s.Length, " length: ")) + .AddConstraint(new DummyValueConstraint(_ => new DummyConstraintResult(node2, "foo2", "bar2"))); + StringBuilder expectationSb = new(); + StringBuilder resultSb = new(); + string expectedResult = (node1 == Outcome.Failure, node2 == Outcome.Failure) switch + { + (true, true) => "bar1 and bar2", + (true, _) => "bar1", + (_, true) => "bar2", + (_, _) => "", + }; + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + result.AppendExpectation(expectationSb); + result.AppendResult(resultSb); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + await That(expectationSb.ToString()).IsEqualTo("foo1 length: foo2"); + await That(resultSb.ToString()).IsEqualTo(expectedResult); + } + + [Fact] + public async Task AddMapping_TryGetValue_ShouldGetValueFromLeftNode() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult("foo", Outcome.Undecided, "foo1", "bar1"))); + node.AddMapping(MemberAccessor.FromFunc(s => s.Length, " length: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(1, Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out string? value); + + await That(result).IsTrue(); + await That(value).IsEqualTo("foo"); + } + + [Fact] + public async Task AddMapping_TryGetValue_ShouldGetValueFromRightNode() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(Outcome.Undecided, "foo1", "bar1"))); + node.AddMapping(MemberAccessor.FromFunc(s => s.Substring(1), " substring: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out string? value); + + await That(result).IsTrue(); + await That(value).IsEqualTo("foobar"); + } + + [Fact] + public async Task AddMapping_TryGetValue_WhenTypeDoesNotMatchAnyNode_ShouldReturnFalse() + { + ExpectationNode node = new(); + node.AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult("foo", Outcome.Undecided, "foo1", "bar1"))); + node.AddMapping(MemberAccessor.FromFunc(s => s.Length, " length: ")) + .AddConstraint(new DummyValueConstraint(_ + => new DummyConstraintResult(42, Outcome.Undecided, "foo2", "bar2"))); + + ConstraintResult constraintResult = await node.IsMetBy("foobar", null!, CancellationToken.None); + bool result = constraintResult.TryGetValue(out DateTime? value); + + await That(result).IsFalse(); + await That(value).IsNull(); + } + + [Fact] + public async Task AddMapping_WithCustomExpectationTextGenerator_ShouldUseIt() + { + ExpectationNode node = new(); + node.AddMapping(MemberAccessor.FromFunc(s => s.Length, " length: "), + (m, sb) => sb.Append("my custom generator:").Append(m)) + .AddConstraint(new DummyConstraint(_ => true, "yeah")); + StringBuilder sb = new(); + + ConstraintResult result = await node.IsMetBy("foobar", null!, CancellationToken.None); + + result.AppendExpectation(sb); + await That(sb.ToString()).IsEqualTo("my custom generator: length: yeah"); + } + [Fact] public async Task AddNode_ShouldThrowNotSupportedException() { @@ -143,6 +386,86 @@ public async Task AppendExpectation_WithMapping_ShouldReturnMapping() await That(sb.ToString()).IsEqualTo(""); } + [Fact] + public async Task Equals_IfConstraintIsDifferent_ShouldBeFalse() + { + ExpectationNode node1 = new(); + node1.AddConstraint(new DummyConstraint("foo")); + ExpectationNode node2 = new(); + node2.AddConstraint(new DummyConstraint("bar")); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_IfConstraintIsTheSame_ShouldBeTrue() + { + ExpectationNode node1 = new(); + node1.AddConstraint(new DummyConstraint("foo")); + ExpectationNode node2 = new(); + node2.AddConstraint(new DummyConstraint("foo")); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreDifferent_ShouldBeFalse() + { + ExpectationNode node1 = new(); + node1 + .AddMapping(MemberAccessor.FromFunc(s => s.Length, " with length1 ")); + ExpectationNode node2 = new(); + node2 + .AddMapping(MemberAccessor.FromFunc(s => s.Length, " with length2 ")); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreSame_ShouldBeTrue() + { + ExpectationNode node1 = new(); + node1 + .AddMapping(MemberAccessor.FromFunc(s => s.Length, " with length ")); + ExpectationNode node2 = new(); + node2 + .AddMapping(MemberAccessor.FromFunc(s => s.Length, " with length ")); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_WhenOtherIsDifferentNode_ShouldBeFalse() + { + ExpectationNode node = new(); + object other = new DummyNode(""); + + bool result = node.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_WhenOtherIsNull_ShouldBeFalse() + { + ExpectationNode node = new(); + + bool result = node.Equals(null); + + await That(result).IsFalse(); + } + [Fact] public async Task IsMetBy_WhenAsyncConstraintReturns_ShouldApplyBecauseReason() { @@ -296,8 +619,8 @@ public async Task { ExpectationNode node = new(); node.AddConstraint( - new DummyValueConstraint( - v => new DummyConstraintResult(Outcome.Failure, v, "foo", "same failure"))); + new DummyValueConstraint(v + => new DummyConstraintResult(Outcome.Failure, v, "foo", "same failure"))); node.AddAsyncMapping(MemberAccessor>.FromFunc(s => Task.FromResult(s), " with mapping ")); node.AddConstraint(new DummyValueConstraint(v => new DummyConstraintResult(Outcome.Failure, 2 * v, "bar", "same failure"))); @@ -339,8 +662,8 @@ public async Task { ExpectationNode node = new(); node.AddConstraint( - new DummyValueConstraint( - v => new DummyConstraintResult(Outcome.Failure, v, "foo", "same failure"))); + new DummyValueConstraint(v + => new DummyConstraintResult(Outcome.Failure, v, "foo", "same failure"))); node.AddMapping(MemberAccessor.FromFunc(s => s, " with mapping ")); node.AddConstraint(new DummyValueConstraint(v => new DummyConstraintResult(Outcome.Failure, 2 * v, "bar", "same failure"))); diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/MappingNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/MappingNodeTests.cs index 0024bd955..0cea92e9a 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/MappingNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/MappingNodeTests.cs @@ -10,6 +10,55 @@ namespace aweXpect.Core.Tests.Core.Nodes; public class MappingNodeTests { + [Fact] + public async Task Equals_IfMemberAccessorsAreDifferent_ShouldBeFalse() + { + MappingNode node1 = new( + MemberAccessor.FromFunc(s => s.Length, " length1 ")); + MappingNode node2 = new( + MemberAccessor.FromFunc(s => s.Length, " length2 ")); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfMemberAccessorsAreSame_ShouldBeTrue() + { + MappingNode node1 = new( + MemberAccessor.FromFunc(s => s.Length, " length ")); + MappingNode node2 = new( + MemberAccessor.FromFunc(s => s.Length, " length ")); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_WhenOtherIsDifferentNode_ShouldBeFalse() + { + MappingNode node = new(MemberAccessor.FromFunc(s => s.Length, " length ")); + object other = new MappingNode(MemberAccessor.FromFunc(s => s * 2, " duplicate ")); + + bool result = node.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_WhenOtherIsNull_ShouldBeFalse() + { + MappingNode node = new(MemberAccessor.FromFunc(s => s.Length, " length ")); + + bool result = node.Equals(null); + + await That(result).IsFalse(); + } + [Fact] public async Task IsMetBy_ShouldUseInnerConstraintWithOuterValue() { diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/OrNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/OrNodeTests.cs index 87dd84931..0052e3dcb 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/OrNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/OrNodeTests.cs @@ -1,6 +1,7 @@ using System.Text; using System.Threading; using aweXpect.Core.Constraints; +using aweXpect.Core.Helpers; using aweXpect.Core.Nodes; using aweXpect.Core.Tests.TestHelpers; @@ -8,10 +9,85 @@ namespace aweXpect.Core.Tests.Core.Nodes; public sealed class OrNodeTests { + [Fact] + public async Task AddAsyncMapping_ShouldUseCurrentNode() + { + MemberAccessor> memberAccessor = + MemberAccessor>.FromExpression(x => Task.FromResult(x.Length)); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + OrNode node = new(first); + + node.AddAsyncMapping(memberAccessor); + node.AddNode(second); + + await That(first.MappingMemberAccessor).IsSameAs(memberAccessor); + await That(second.MappingMemberAccessor).IsNull(); + } + + [Fact] + public async Task AddAsyncMapping_ShouldUseSecondNode() + { + MemberAccessor> memberAccessor = + MemberAccessor>.FromExpression(x => Task.FromResult(x.Length)); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + OrNode node = new(first); + node.AddNode(second); + + node.AddAsyncMapping(memberAccessor); + + await That(first.MappingMemberAccessor).IsNull(); + await That(second.MappingMemberAccessor).IsSameAs(memberAccessor); + } + + [Fact] + public async Task AddMapping_ShouldUseCurrentNode() + { + MemberAccessor memberAccessor = MemberAccessor.FromExpression(x => x.Length); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + OrNode node = new(first); + + node.AddMapping(memberAccessor); + node.AddNode(second); + + await That(first.MappingMemberAccessor).IsSameAs(memberAccessor); + await That(second.MappingMemberAccessor).IsNull(); + } + + [Fact] + public async Task AddMapping_ShouldUseSecondNode() + { + MemberAccessor memberAccessor = MemberAccessor.FromExpression(x => x.Length); + DummyNode first = new("foo"); + DummyNode second = new("bar"); + OrNode node = new(first); + node.AddNode(second); + + node.AddMapping(memberAccessor); + + await That(first.MappingMemberAccessor).IsNull(); + await That(second.MappingMemberAccessor).IsSameAs(memberAccessor); + } + + [Fact] + public async Task AppendExpectation_WithAdditionalNodes_ShouldUseAllNodes() + { + OrNode node = new(new DummyNode("foo")); + node.AddNode(new DummyNode("bar")); + node.AddNode(new DummyNode("baz")); + StringBuilder sb = new(); + + node.AppendExpectation(sb); + + await That(sb.ToString()).IsEqualTo("foo or bar or baz"); + } + [Fact] public async Task AppendExpectation_WithoutAdditionalNodes_ShouldUseFirstNode() { - AndNode node = new(new DummyNode("foo")); + OrNode node = new(new DummyNode("foo")); StringBuilder sb = new(); node.AppendExpectation(sb); @@ -19,11 +95,143 @@ public async Task AppendExpectation_WithoutAdditionalNodes_ShouldUseFirstNode() await That(sb.ToString()).IsEqualTo("foo"); } + [Fact] + public async Task Equals_IfCurrentNodeIsDifferent_ShouldBeFalse() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + OrNode node1 = new(innerNode1); + OrNode node2 = new(innerNode2); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfCurrentNodeIsTheSame_ShouldBeTrue() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + OrNode node1 = new(innerNode1); + OrNode node2 = new(innerNode2); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreDifferent_ShouldBeFalse() + { + DummyNode innerNode0 = new("0", () => new DummyConstraintResult(Outcome.Success, "0", "")); + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode currentNode = new("3", () => new DummyConstraintResult(Outcome.Success, "3", "")); + OrNode node1 = new(innerNode0); + node1.AddNode(innerNode1); + node1.AddNode(currentNode); + OrNode node2 = new(innerNode0); + node2.AddNode(innerNode2); + node2.AddNode(currentNode); + + bool result = node1.Equals(node2); + + await That(result).IsFalse(); + await That(node1.GetHashCode()).IsNotEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_IfInnerNodesAreSame_ShouldBeTrue() + { + DummyNode innerNode1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode2 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode innerNode3 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode innerNode4 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode currentNode = new("3", () => new DummyConstraintResult(Outcome.Success, "3", "")); + OrNode node1 = new(innerNode1); + node1.AddNode(innerNode3); + node1.AddNode(currentNode); + OrNode node2 = new(innerNode2); + node2.AddNode(innerNode4); + node2.AddNode(currentNode); + + bool result = node1.Equals(node2); + + await That(result).IsTrue(); + await That(node1.GetHashCode()).IsEqualTo(node2.GetHashCode()); + } + + [Fact] + public async Task Equals_WhenOtherIsDifferentNode_ShouldBeFalse() + { + DummyNode inner = new("foo"); + OrNode node = new(inner); + object other = new AndNode(inner); + + bool result = node.Equals(other); + + await That(result).IsFalse(); + } + + [Fact] + public async Task Equals_WhenOtherIsNull_ShouldBeFalse() + { + OrNode node = new(new DummyNode("foo")); + + bool result = node.Equals(null); + + await That(result).IsFalse(); + } + + [Theory] + [InlineData(Outcome.Success, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Success, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Success)] + [InlineData(Outcome.Success, Outcome.Undecided, Outcome.Failure)] + [InlineData(Outcome.Undecided, Outcome.Success, Outcome.Failure)] + [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Undecided)] + [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Undecided)] + [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] + public async Task NegatedOutcome_ShouldBeExpected(Outcome node1, Outcome node2, Outcome expectedOutcome) + { + OrNode node = new(new DummyNode("", () => new DummyConstraintResult(node1))); + node.AddNode(new DummyNode("", () => new DummyConstraintResult(node2))); + + ConstraintResult result = await node.IsMetBy(0, null!, CancellationToken.None); + result.Negate(); + + await That(result.Outcome).IsEqualTo(expectedOutcome); + } + + [Fact] + public async Task NegatedResult_ShouldUseAndAsSeparator() + { + OrNode node = new(new DummyNode("", () => new DummyConstraintResult(Outcome.Success, "foo"))); + node.AddNode(new DummyNode("", () => new DummyConstraintResult(Outcome.Success, "bar"))); + StringBuilder sb1 = new(); + StringBuilder sb2 = new(); + + ConstraintResult result = await node.IsMetBy(0, null!, CancellationToken.None); + result.AppendExpectation(sb1); + + result.Negate(); + + result.AppendExpectation(sb2); + await That(sb1.ToString()).IsEqualTo("foo or bar"); + await That(sb2.ToString()).IsEqualTo("foo and bar"); + } + [Theory] [InlineData(Outcome.Success, Outcome.Success, Outcome.Success)] [InlineData(Outcome.Failure, Outcome.Success, Outcome.Success)] [InlineData(Outcome.Success, Outcome.Failure, Outcome.Success)] [InlineData(Outcome.Failure, Outcome.Failure, Outcome.Failure)] + [InlineData(Outcome.Success, Outcome.Undecided, Outcome.Success)] + [InlineData(Outcome.Undecided, Outcome.Success, Outcome.Success)] [InlineData(Outcome.Failure, Outcome.Undecided, Outcome.Undecided)] [InlineData(Outcome.Undecided, Outcome.Failure, Outcome.Undecided)] [InlineData(Outcome.Undecided, Outcome.Undecided, Outcome.Undecided)] @@ -37,6 +245,50 @@ public async Task Outcome_ShouldBeExpected(Outcome node1, Outcome node2, Outcome await That(result.Outcome).IsEqualTo(expectedOutcome); } + [Fact] + public async Task SetReason_WithAdditionalNodes_ShouldUseCurrentNode() + { + DummyNode node1 = new("node1"); + DummyNode node2 = new("node2"); + DummyNode current = new("current"); + OrNode node = new(node1); + node.AddNode(node2); + node.AddNode(current); + + node.SetReason(new BecauseReason("bar")); + + await That(current.ReceivedReason).IsEqualTo(", because bar"); + await That(node1.ReceivedReason).IsNull(); + await That(node2.ReceivedReason).IsNull(); + } + + [Fact] + public async Task SetReason_WithAdditionalNodes_WhenCurrentNodeIsEmptyExpectationNode_ShouldUseLastNode() + { + DummyNode node1 = new("node1"); + DummyNode node2 = new("node2"); + ExpectationNode current = new(); + OrNode node = new(node1); + node.AddNode(node2); + node.AddNode(current); + + node.SetReason(new BecauseReason("bar")); + + await That(node1.ReceivedReason).IsNull(); + await That(node2.ReceivedReason).IsEqualTo(", because bar"); + } + + [Fact] + public async Task SetReason_WithoutAdditionalNodes_ShouldSetReasonForCurrentNode() + { + DummyNode current = new("current"); + OrNode node = new(current); + + node.SetReason(new BecauseReason("bar")); + + await That(current.ReceivedReason).IsEqualTo(", because bar"); + } + [Fact] public async Task ShouldConsiderFurtherProcessingStrategy() { diff --git a/Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs b/Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs index 1ba95c341..6266e447c 100644 --- a/Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs @@ -136,8 +136,8 @@ public async Task CombinedResult_ShouldBeNegatable(Outcome node1, Outcome node2, [Fact] public async Task Equals_IfInnerAreDifferent_ShouldBeFalse() { - DummyNode node1 = new("", () => new DummyConstraintResult(Outcome.Success, "1", "")); - DummyNode node2 = new("", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode node1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode node2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); WhichNode whichNode1 = new(null, s => s.Length); whichNode1.AddNode(node1); WhichNode whichNode2 = new(null, s => s.Length); @@ -151,7 +151,7 @@ public async Task Equals_IfInnerAreDifferent_ShouldBeFalse() [Fact] public async Task Equals_IfInnerAreSame_ShouldBeTrue() { - DummyNode node1 = new("", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode node1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); WhichNode whichNode1 = new(null, s => s.Length); whichNode1.AddNode(node1); WhichNode whichNode2 = new(null, s => s.Length); @@ -177,8 +177,8 @@ public async Task Equals_IfParentsAreBothNull_ShouldBeTrue() [Fact] public async Task Equals_IfParentsAreDifferent_ShouldBeFalse() { - DummyNode node1 = new("", () => new DummyConstraintResult(Outcome.Success, "1", "")); - DummyNode node2 = new("", () => new DummyConstraintResult(Outcome.Success, "2", "")); + DummyNode node1 = new("1", () => new DummyConstraintResult(Outcome.Success, "1", "")); + DummyNode node2 = new("2", () => new DummyConstraintResult(Outcome.Success, "2", "")); WhichNode whichNode1 = new(node1, s => s.Length); WhichNode whichNode2 = new(node2, s => s.Length); diff --git a/Tests/aweXpect.Core.Tests/TestHelpers/DummyConstraintResult.cs b/Tests/aweXpect.Core.Tests/TestHelpers/DummyConstraintResult.cs index 45c7c7a91..1c9ecc19f 100644 --- a/Tests/aweXpect.Core.Tests/TestHelpers/DummyConstraintResult.cs +++ b/Tests/aweXpect.Core.Tests/TestHelpers/DummyConstraintResult.cs @@ -8,6 +8,7 @@ public sealed class DummyConstraintResult : ConstraintResult { private readonly string? _expectationText; private readonly string? _failureText; + private readonly object? _value; public DummyConstraintResult(Outcome outcome, string? expectationText = null, @@ -20,6 +21,16 @@ public DummyConstraintResult(Outcome outcome, _failureText = failureText; } + public DummyConstraintResult(object value, + Outcome outcome, + string? expectationText = null, + string? failureText = null, + FurtherProcessingStrategy furtherProcessingStrategy = FurtherProcessingStrategy.Continue) + : this(outcome, expectationText, failureText, furtherProcessingStrategy) + { + _value = value; + } + public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) { if (_expectationText != null) @@ -38,6 +49,12 @@ public override void AppendResult(StringBuilder stringBuilder, string? indentati public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default { + if (_value is TValue typedValue) + { + value = typedValue; + return true; + } + value = default; return false; } diff --git a/Tests/aweXpect.Core.Tests/TestHelpers/DummyNode.cs b/Tests/aweXpect.Core.Tests/TestHelpers/DummyNode.cs index 78a5b9443..89702eecc 100644 --- a/Tests/aweXpect.Core.Tests/TestHelpers/DummyNode.cs +++ b/Tests/aweXpect.Core.Tests/TestHelpers/DummyNode.cs @@ -9,7 +9,9 @@ namespace aweXpect.Core.Tests.TestHelpers; internal class DummyNode(string name, Func? result = null) : Node { + private readonly string _name = name; public MemberAccessor? MappingMemberAccessor { get; private set; } + public string? ReceivedReason { get; private set; } public override void AddConstraint(IConstraint constraint) => throw new NotSupportedException(); @@ -45,8 +47,16 @@ public override Task IsMetBy( => result == null ? throw new NotSupportedException() : Task.FromResult(result()); public override void SetReason(BecauseReason becauseReason) - => throw new NotSupportedException(); + => ReceivedReason = becauseReason.ToString(); public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(name); + => stringBuilder.Append(_name); + + /// + public override bool Equals(object? obj) => obj is DummyNode other && Equals(other); + + private bool Equals(DummyNode other) => _name == other._name; + + /// + public override int GetHashCode() => _name.GetHashCode(); }