From 029021d6cab380d2176b138d46328e876ee52f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Sat, 12 Mar 2022 11:08:36 +0100 Subject: [PATCH] Split `AssertionScope` specs in multiple files --- .../Execution/AssertionScopeSpecs.cs | 974 +----------------- .../Execution/ChainingApiSpecs.cs | 418 ++++++++ .../Execution/MessageFormatingSpecs.cs | 563 ++++++++++ 3 files changed, 982 insertions(+), 973 deletions(-) create mode 100644 Tests/FluentAssertions.Specs/Execution/ChainingApiSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Execution/MessageFormatingSpecs.cs diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs index d33c78265a..ea981dae8e 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -16,8 +15,6 @@ namespace FluentAssertions.Specs.Execution { public class AssertionScopeSpecs { - #region Lifecycle Management - [Fact] public void When_disposed_it_should_throw_any_failures() { @@ -240,7 +237,7 @@ public void When_using_a_custom_strategy_it_should_include_failure_messages_of_a public class CustomAssertionStrategy : IAssertionStrategy { - private readonly List failureMessages = new List(); + private readonly List failureMessages = new(); public IEnumerable FailureMessages => failureMessages; @@ -290,975 +287,6 @@ public void ThrowIfAny(IDictionary context) // do nothing } } - - #endregion - - #region Message Formatting - - [Fact] - public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once() - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("Failure"); - AssertionScope.Current.FailWith("Failure"); - - using (var nestedScope = new AssertionScope()) - { - nestedScope.FailWith("Failure"); - nestedScope.FailWith("Failure"); - } - - // Act - Action act = scope.Dispose; - - // Assert - try - { - act(); - } - catch (Exception exception) - { - int matches = new Regex(".*Failure.*").Matches(exception.Message).Count; - - matches.Should().Be(4); - } - } - - [Fact] - public void The_failure_message_should_use_the_name_of_the_scope_as_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope("foo"); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected foo to be equal to*"); - } - - [Fact] - public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(new Lazy(() => "lazy foo")); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected lazy foo to be equal to*"); - } - - [Fact] - public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new Dictionary(); - values.Should().ContainKey(0); - values.Should().ContainKey(1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); - } - - [Fact] - public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new List(); - values.Should().ContainSingle(); - values.Should().ContainSingle(); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected*to contain a single item, but the collection is empty*" + - "Expected*to contain a single item, but the collection is empty*"); - } - - [Fact] - public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeOfType(); - item.Should().BeOfType(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected type to be System.Int32, but found System.String.*" + - "Expected type to be System.Int64, but found System.String."); - } - - [Fact] - public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeAssignableTo(); - item.Should().BeAssignableTo(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to be assignable to System.Int32, but System.String is not.*" + - "Expected * to be assignable to System.Int64, but System.String is not."); - } - - [Fact] - public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly() - { - // Act - Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); - - // Assert - act.Should().Throw() - .WithMessage("*because can't use these in becauseArgs: { }*"); - } - - [Fact] - public void When_becauseArgs_is_null_it_should_render_reason_correctly() - { - // Act - object[] becauseArgs = null; - Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); - - // Assert - act.Should().Throw() - .WithMessage("*it should still work*"); - } - - [Fact] - public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly() - { - // Act - Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters"); - - // Assert - act.Should().Throw() - .WithMessage("*because use of {} is okay if there are no because parameters*"); - } - - [Fact] - public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text() - { - // Act - Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter"); - - // Assert - act.Should().Throw() - .WithMessage("*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); - } - - [Fact] - public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope("{}"); - default(int[]).Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected {} to be equal to*"); - } - - [Fact] - public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly() - { - // Act - Action act = () => "{foo}".Should().Be("{bar}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); - } - - [Fact] - public void When_message_contains_double_braces_they_should_not_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("{{empty}}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*empty*"); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [Theory] - public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [Theory] - public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("\\{0}\\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData("\\\\n")] - [InlineData("\\\\\n")] - [Theory] - public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData("\\\\n")] - [InlineData("\\\\\n")] - [Theory] - public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("\\{0}\\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - - [Fact] - public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() - { - // Arrange / Act - Action act = () => "A\\".Should().Be("A"); - - // Assert - act.Should().Throw() - .WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message"); - } - - [Fact] - public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() - { - // Arrange / Act - Action act = () => "A".Should().Be("A\\"); - - // Assert - act.Should().Throw() - .WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message"); - } - - [Fact] - public void When_message_starts_with_single_braces_they_should_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("MyKey", "MyValue"); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("MyValue*"); - } - - [Fact] - public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("SomeValueAnotherValue*"); - } - - [Fact] - public void When_adding_reportable_values_they_should_be_reported_after_the_message() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); - } - - [Fact] - public void When_adding_non_reportable_value_it_should_not_be_reported_after_the_message() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - AssertionScope.Current.FailWith("{SomeKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .Which.Message.Should().NotContain("With SomeKey:\nSomeValue"); - } - - [Fact] - public void When_adding_non_reportable_value_it_should_be_retrievable_from_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().Be("SomeValue"); - } - - [Fact] - public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there_are_no_failures() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - // Act - scope.Dispose(); - - // Assert - deferredValueInvoked.Should().BeFalse(); - } - - [Fact] - public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_a_failure() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*MyValue*"); - deferredValueInvoked.Should().BeTrue(); - } - - [Fact] - public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_message() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void When_an_expectation_with_arguments_is_defined_it_should_be_preceeding_the_failure_message() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_object() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected object"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_inline_declared_fallback_identifier() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context:fallback}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected fallback"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_defined_default_identifier() - { - // Act - Action act = () => Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void The_failure_message_should_contain_the_reason() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void The_failure_message_should_contain_the_reason_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - #endregion - - #region Chaining API - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one() - { - bool succeeded = false; - - // Act - try - { - Execute.Assertion - .ForCondition(condition: true) - .FailWith("First assertion") - .Then - .FailWith("Second assertion"); - } - catch (Exception e) - { - // Assert - succeeded = (e is XunitException xUnitException) && xUnitException.Message.Contains("Second"); - } - - if (!succeeded) - { - throw new XunitException("Expected the second assertion to fail"); - } - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() - { - // Act - Action act = () => Execute.Assertion - .Given(() => new[] { "a", "b" }) - .ForCondition(collection => collection.Length > 0) - .FailWith("First assertion"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() - { - // Arrange - using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); - - // Act / Assert - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() - { - // Arrange - bool secondConditionEvaluated = false; - try - { - using var _ = new AssertionScope(); - - // Act - Execute.Assertion - .Given(() => (string)null) - .ForCondition(s => s is not null) - .FailWith("but is was null") - .Then - .ForCondition(s => secondConditionEvaluated = true) - .FailWith("it should be 42"); - } - catch - { - // Ignore - } - - // Assert - secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() - { - // Arrange - using var scope = new AssertionScope(); - - // Act - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .ForCondition(false) - .FailWith("Second assertion"); - - string[] failures = scope.Discard(); - scope.Dispose(); - - Assert.Single(failures); - Assert.Contains("First assertion", failures); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_with_arguments_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - - // Assert - Assert.Throws(act); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the root of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the \"root\" of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(true) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected other"); - } - - #endregion } } diff --git a/Tests/FluentAssertions.Specs/Execution/ChainingApiSpecs.cs b/Tests/FluentAssertions.Specs/Execution/ChainingApiSpecs.cs new file mode 100644 index 0000000000..e11f9fc6c1 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/ChainingApiSpecs.cs @@ -0,0 +1,418 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution +{ + public class ChainingApiSpecs + { + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one() + { + bool succeeded = false; + + // Act + try + { + Execute.Assertion + .ForCondition(condition: true) + .FailWith("First assertion") + .Then + .FailWith("Second assertion"); + } + catch (Exception e) + { + // Assert + succeeded = (e is XunitException xUnitException) && xUnitException.Message.Contains("Second"); + } + + if (!succeeded) + { + throw new XunitException("Expected the second assertion to fail"); + } + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() + { + // Act + Action act = () => Execute.Assertion + .Given(() => new[] { "a", "b" }) + .ForCondition(collection => collection.Length > 0) + .FailWith("First assertion"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() + { + // Arrange + using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); + + // Act / Assert + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() + { + // Arrange + bool secondConditionEvaluated = false; + try + { + using var _ = new AssertionScope(); + + // Act + Execute.Assertion + .Given(() => (string)null) + .ForCondition(s => s is not null) + .FailWith("but is was null") + .Then + .ForCondition(_ => secondConditionEvaluated = true) + .FailWith("it should be 42"); + } + catch + { + // Ignore + } + + // Assert + secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() + { + // Arrange + using var scope = new AssertionScope(); + + // Act + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .ForCondition(false) + .FailWith("Second assertion"); + + string[] failures = scope.Discard(); + scope.Dispose(); + + Assert.Single(failures); + Assert.Contains("First assertion", failures); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ") + .FailWith("of all evil"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root") + .FailWith("of all evil"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_resons_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_resons_with_arguments_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + + // Assert + Assert.Throws(act); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ") + .FailWith("of all evil"); + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the root of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root") + .FailWith("of all evil"); + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the \"root\" of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(true) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected other"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/MessageFormatingSpecs.cs b/Tests/FluentAssertions.Specs/Execution/MessageFormatingSpecs.cs new file mode 100644 index 0000000000..f500637884 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/MessageFormatingSpecs.cs @@ -0,0 +1,563 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution +{ + public class MessageFormatingSpecs + { + [Fact] + public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once() + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("Failure"); + AssertionScope.Current.FailWith("Failure"); + + using (var nestedScope = new AssertionScope()) + { + nestedScope.FailWith("Failure"); + nestedScope.FailWith("Failure"); + } + + // Act + Action act = scope.Dispose; + + // Assert + try + { + act(); + } + catch (Exception exception) + { + int matches = new Regex(".*Failure.*").Matches(exception.Message).Count; + + matches.Should().Be(4); + } + } + + [Fact] + public void The_failure_message_should_use_the_name_of_the_scope_as_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope("foo"); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected foo to be equal to*"); + } + + [Fact] + public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(new Lazy(() => "lazy foo")); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected lazy foo to be equal to*"); + } + + [Fact] + public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new Dictionary(); + values.Should().ContainKey(0); + values.Should().ContainKey(1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); + } + + [Fact] + public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new List(); + values.Should().ContainSingle(); + values.Should().ContainSingle(); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain a single item, but the collection is empty*" + + "Expected*to contain a single item, but the collection is empty*"); + } + + [Fact] + public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeOfType(); + item.Should().BeOfType(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected type to be System.Int32, but found System.String.*" + + "Expected type to be System.Int64, but found System.String."); + } + + [Fact] + public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeAssignableTo(); + item.Should().BeAssignableTo(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to be assignable to System.Int32, but System.String is not.*" + + "Expected * to be assignable to System.Int64, but System.String is not."); + } + + [Fact] + public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly() + { + // Act + Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); + + // Assert + act.Should().Throw() + .WithMessage("*because can't use these in becauseArgs: { }*"); + } + + [Fact] + public void When_becauseArgs_is_null_it_should_render_reason_correctly() + { + // Act + object[] becauseArgs = null; + Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); + + // Assert + act.Should().Throw() + .WithMessage("*it should still work*"); + } + + [Fact] + public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly() + { + // Act + Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters"); + + // Assert + act.Should().Throw() + .WithMessage("*because use of {} is okay if there are no because parameters*"); + } + + [Fact] + public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text() + { + // Act + Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter"); + + // Assert + act.Should().Throw() + .WithMessage("*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); + } + + [Fact] + public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope("{}"); + default(int[]).Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected {} to be equal to*"); + } + + [Fact] + public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly() + { + // Act + Action act = () => "{foo}".Should().Be("{bar}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); + } + + [Fact] + public void When_message_contains_double_braces_they_should_not_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("{{empty}}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*empty*"); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] + [Theory] + public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith(str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage(str); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] + [Theory] + public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("\\{0}\\A", str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("\\\"" + str + "\"\\A*"); + } + + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData("\\\\n")] + [InlineData("\\\\\n")] + [Theory] + public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith(str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage(str); + } + + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData("\\\\n")] + [InlineData("\\\\\n")] + [Theory] + public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("\\{0}\\A", str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("\\\"" + str + "\"\\A*"); + } + + [Fact] + public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + { + // Arrange / Act + Action act = () => "A\\".Should().Be("A"); + + // Assert + act.Should().Throw() + .WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message"); + } + + [Fact] + public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + { + // Arrange / Act + Action act = () => "A".Should().Be("A\\"); + + // Assert + act.Should().Throw() + .WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message"); + } + + [Fact] + public void When_message_starts_with_single_braces_they_should_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("MyKey", "MyValue"); + + AssertionScope.Current.FailWith("{MyKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("MyValue*"); + } + + [Fact] + public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("SomeKey", "SomeValue"); + scope.AddReportable("AnotherKey", "AnotherValue"); + + AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("SomeValueAnotherValue*"); + } + + [Fact] + public void When_adding_reportable_values_they_should_be_reported_after_the_message() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("SomeKey", "SomeValue"); + scope.AddReportable("AnotherKey", "AnotherValue"); + + AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); + } + + [Fact] + public void When_adding_non_reportable_value_it_should_not_be_reported_after_the_message() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + + AssertionScope.Current.FailWith("{SomeKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .Which.Message.Should().NotContain("With SomeKey:\nSomeValue"); + } + + [Fact] + public void When_adding_non_reportable_value_it_should_be_retrievable_from_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().Be("SomeValue"); + } + + [Fact] + public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there_are_no_failures() + { + // Arrange + var scope = new AssertionScope(); + var deferredValueInvoked = false; + + scope.AddReportable("MyKey", () => + { + deferredValueInvoked = true; + + return "MyValue"; + }); + + // Act + scope.Dispose(); + + // Assert + deferredValueInvoked.Should().BeFalse(); + } + + [Fact] + public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_a_failure() + { + // Arrange + var scope = new AssertionScope(); + var deferredValueInvoked = false; + + scope.AddReportable("MyKey", () => + { + deferredValueInvoked = true; + + return "MyValue"; + }); + + AssertionScope.Current.FailWith("{MyKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*MyValue*"); + deferredValueInvoked.Should().BeTrue(); + } + + [Fact] + public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_message() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(false) + .FailWith("of disappointment"); + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void When_an_expectation_with_arguments_is_defined_it_should_be_preceeding_the_failure_message() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(false) + .FailWith("of disappointment"); + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_object() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected object"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_inline_declared_fallback_identifier() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(false) + .FailWith("Expected {context:fallback}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected fallback"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_defined_default_identifier() + { + // Act + Action act = () => Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void The_failure_message_should_contain_the_reason() + { + // Act + Action act = () => Execute.Assertion + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void The_failure_message_should_contain_the_reason_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + } +}