diff --git a/Src/FluentAssertions/Primitives/StringAssertions.cs b/Src/FluentAssertions/Primitives/StringAssertions.cs index 45eca368c8..5094a57bac 100644 --- a/Src/FluentAssertions/Primitives/StringAssertions.cs +++ b/Src/FluentAssertions/Primitives/StringAssertions.cs @@ -276,10 +276,13 @@ public AndConstraint MatchRegex([RegexPattern] string regularE .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to match regex {0}{reason}, but it was .", regularExpression); - bool isMatch = false; try { - isMatch = Regex.IsMatch(Subject, regularExpression); + Execute.Assertion + .ForCondition(Regex.IsMatch(Subject, regularExpression)) + .BecauseOf(because, becauseArgs) + .UsingLineBreaks + .FailWith("Expected {context:string} to match regex {0}{reason}, but {1} does not match.", regularExpression, Subject); } catch (ArgumentException) { @@ -287,12 +290,6 @@ public AndConstraint MatchRegex([RegexPattern] string regularE .FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", regularExpression); } - Execute.Assertion - .ForCondition(isMatch) - .BecauseOf(because, becauseArgs) - .UsingLineBreaks - .FailWith("Expected {context:string} to match regex {0}{reason}, but {1} does not match.", regularExpression, Subject); - return new AndConstraint(this); } @@ -319,10 +316,13 @@ public AndConstraint NotMatchRegex([RegexPattern] string regul .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to not match regex {0}{reason}, but it was .", regularExpression); - bool isMatch = false; try { - isMatch = Regex.IsMatch(Subject, regularExpression); + Execute.Assertion + .ForCondition(!Regex.IsMatch(Subject, regularExpression)) + .BecauseOf(because, becauseArgs) + .UsingLineBreaks + .FailWith("Did not expect {context:string} to match regex {0}{reason}, but {1} matches.", regularExpression, Subject); } catch (ArgumentException) { @@ -330,12 +330,6 @@ public AndConstraint NotMatchRegex([RegexPattern] string regul regularExpression); } - Execute.Assertion - .ForCondition(!isMatch) - .BecauseOf(because, becauseArgs) - .UsingLineBreaks - .FailWith("Did not expect {context:string} to match regex {0}{reason}, but {1} matches.", regularExpression, Subject); - return new AndConstraint(this); } diff --git a/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs b/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs index 00cb51936c..207bf048a8 100644 --- a/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs +++ b/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs @@ -1032,6 +1032,24 @@ public void When_wait_time_is_negative_for_async_func_executed_with_wait_it_shou .WithMessage("* value of waitTime must be non-negative*"); } + [Fact] + public void When_wait_time_is_zero_for_async_func_executed_with_wait_it_should_not_throw() + { + // Arrange + var waitTime = 0.Milliseconds(); + var pollInterval = 10.Milliseconds(); + + var clock = new FakeClock(); + var asyncObject = new AsyncClass(); + Func someFunc = () => asyncObject.SucceedAsync(); + + // Act + Action act = () => someFunc.Should(clock).NotThrowAfter(waitTime, pollInterval); + + // Assert + act.Should().NotThrow(); + } + [Fact] public void When_poll_interval_is_negative_for_async_func_executed_with_wait_it_should_throw() { @@ -1050,6 +1068,24 @@ public void When_poll_interval_is_negative_for_async_func_executed_with_wait_it_ .WithMessage("* value of pollInterval must be non-negative*"); } + [Fact] + public void When_poll_interval_is_zero_for_async_func_executed_with_wait_it_should_not_throw() + { + // Arrange + var waitTime = 10.Milliseconds(); + var pollInterval = 0.Milliseconds(); + + var clock = new FakeClock(); + var asyncObject = new AsyncClass(); + Func someFunc = () => asyncObject.SucceedAsync(); + + // Act + Action act = () => someFunc.Should(clock).NotThrowAfter(waitTime, pollInterval); + + // Assert + act.Should().NotThrow(); + } + [Fact] public void When_no_exception_should_be_thrown_for_async_func_executed_with_wait_after_wait_time_but_it_was_it_should_throw() diff --git a/Tests/Shared.Specs/CollectionAssertionSpecs.cs b/Tests/Shared.Specs/CollectionAssertionSpecs.cs index 7fe9e6ecd3..092b40f91e 100644 --- a/Tests/Shared.Specs/CollectionAssertionSpecs.cs +++ b/Tests/Shared.Specs/CollectionAssertionSpecs.cs @@ -197,6 +197,32 @@ public void When_collection_count_is_matched_against_a_predicate_and_collection_ "Expected collection to contain (c < 3) items because we want to test the behaviour with a null subject, but found ."); } + [Fact] + public void When_collection_count_is_matched_against_a_predicate_it_should_not_throw() + { + // Arrange + IEnumerable collection = new[] { 1, 2, 3 }; + + // Act + Action act = () => collection.Should().HaveCount(c => c % 2 == 1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_collection_count_is_matched_against_a_predicate_it_should_throw() + { + // Arrange + IEnumerable collection = new[] { 1, 2, 3 }; + + // Act + Action act = () => collection.Should().HaveCount(c => c % 2 == 0); + + // Assert + act.Should().Throw(); + } + [Fact] public void When_counting_nongeneric_enumerable_it_should_enumerate() { diff --git a/Tests/Shared.Specs/DateTimeOffsetValueFormatterSpecs.cs b/Tests/Shared.Specs/DateTimeOffsetValueFormatterSpecs.cs index 27433a2ac0..e453641fc2 100644 --- a/Tests/Shared.Specs/DateTimeOffsetValueFormatterSpecs.cs +++ b/Tests/Shared.Specs/DateTimeOffsetValueFormatterSpecs.cs @@ -73,6 +73,27 @@ public void When_date_is_not_relevant_it_should_not_be_included_in_the_output() result.Should().Be("<08:20:01>"); } + [InlineData("0001-01-02 04:05:06", "<0001-01-02 04:05:06>")] + [InlineData("0001-02-01 04:05:06", "<0001-02-01 04:05:06>")] + [InlineData("0002-01-01 04:05:06", "<0002-01-01 04:05:06>")] + [InlineData("0001-02-02 04:05:06", "<0001-02-02 04:05:06>")] + [InlineData("0002-01-02 04:05:06", "<0002-01-02 04:05:06>")] + [InlineData("0002-02-01 04:05:06", "<0002-02-01 04:05:06>")] + [InlineData("0002-02-02 04:05:06", "<0002-02-02 04:05:06>")] + [Theory] + public void When_date_is_relevant_it_should_be_included_in_the_output(string actual, string expected) + { + // Arrange + var formatter = new DateTimeOffsetValueFormatter(); + var value = DateTime.Parse(actual, CultureInfo.InvariantCulture); + + // Act + string result = formatter.Format(value, new FormattingContext(), null); + + // Assert + result.Should().Be(expected); + } + [Fact] public void When_a_full_date_and_time_is_specified_all_parts_should_be_included_in_the_output() { diff --git a/Tests/Shared.Specs/GenericDictionaryAssertionSpecs.cs b/Tests/Shared.Specs/GenericDictionaryAssertionSpecs.cs index 432f97a709..b8111c07da 100644 --- a/Tests/Shared.Specs/GenericDictionaryAssertionSpecs.cs +++ b/Tests/Shared.Specs/GenericDictionaryAssertionSpecs.cs @@ -1525,6 +1525,24 @@ public void When_a_dictionary_contains_a_list_of_keys_it_should_throw_with_clear "Expected dictionary {[1, One], [2, Two]} to not contain key {2, 3} because we do, but found {2}."); } + [Fact] + public void When_a_dictionary_contains_exactly_one_of_the_keys_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys(new[] { 2 }, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1, One], [2, Two]} to not contain key 2 because we do."); + } + [Fact] public void When_the_noncontents_of_a_dictionary_are_checked_against_an_empty_list_of_keys_it_should_throw_clear_explanation() { @@ -1763,6 +1781,24 @@ public void When_dictionary_does_not_contain_multiple_values_that_is_not_in_the_ act.Should().NotThrow(); } + [Fact] + public void When_a_dictionary_contains_a_exactly_one_of_the_values_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValues(new[] { "Two" }, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1, One], [2, Two]} to not contain value \"Two\" because we do."); + } + [Fact] public void When_a_dictionary_contains_a_number_of_values_it_should_throw_with_clear_explanation() { diff --git a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs index 0c1792bbfd..69cb294658 100644 --- a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs @@ -229,6 +229,32 @@ public void When_object_is_not_of_the_unexpected_open_generic_type_it_should_not action.Should().NotThrow(); } + [Fact] + public void When_generic_object_is_not_of_the_unexpected_type_it_should_not_throw() + { + // Arrange + var aList = new System.Collections.Generic.List(); + + // Act + Action action = () => aList.Should().NotBeOfType(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_non_generic_object_is_not_of_the_unexpected_open_generic_type_it_should_not_throw() + { + // Arrange + var aString = "blah"; + + // Act + Action action = () => aString.Should().NotBeOfType(typeof(System.Collections.Generic.Dictionary<,>)); + + // Assert + action.Should().NotThrow(); + } + [Fact] public void When_asserting_object_is_not_of_type_and_it_is_null_it_should_throw() { diff --git a/Tests/Shared.Specs/StringAssertionSpecs.cs b/Tests/Shared.Specs/StringAssertionSpecs.cs index e3255d30c3..f917643743 100644 --- a/Tests/Shared.Specs/StringAssertionSpecs.cs +++ b/Tests/Shared.Specs/StringAssertionSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -458,6 +459,19 @@ public void When_a_string_does_match_the_equivalent_of_a_wildcard_pattern_it_sho act.Should().NotThrow(); } + [Fact] + public void When_a_string_with_newline_matches_the_equivalent_of_a_wildcard_pattern_it_should_not_throw() + { + // Arrange + string subject = "hello\r\nworld!"; + + // Act + Action act = () => subject.Should().MatchEquivalentOf("helloworld!"); + + // Assert + act.Should().NotThrow(); + } + #endregion #region Not Match Equivalent Of @@ -496,6 +510,19 @@ public void When_a_string_does_match_the_equivalent_of_a_pattern_but_it_shouldnt #endif } + [Fact] + public void When_a_string_with_newlines_does_match_the_equivalent_of_a_pattern_but_it_shouldnt_it_should_throw() + { + // Arrange + string subject = "hello\r\nworld!"; + + // Act + Action act = () => subject.Should().NotMatchEquivalentOf("helloworld!"); + + // Assert + act.Should().Throw(); + } + #endregion #region Match Regex @@ -585,6 +612,28 @@ public void When_a_string_is_matched_against_an_invalid_regex_it_should_throw_wi #endif } + [Fact] + public void When_a_string_is_matched_against_an_invalid_regex_it_should_only_have_one_failure_message() + { + // Arrange + string subject = "hello world!"; + string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors + + // Act + Action act = () => + { + using (new AssertionScope()) + { + subject.Should().MatchRegex(invalidRegex); + } + }; + + // Assert + act.Should().Throw() + .Which.Message.Should().Contain("is not a valid regular expression") + .And.NotContain("does not match"); + } + #endregion #region Not Match Regex @@ -672,6 +721,28 @@ public void When_a_string_is_negatively_matched_against_an_invalid_regex_it_shou #endif } + [Fact] + public void When_a_string_is_negatively_matched_against_an_invalid_regex_it_only_contain_one_failure_message() + { + // Arrange + string subject = "hello world!"; + string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors + + // Act + Action act = () => + { + using (new AssertionScope()) + { + subject.Should().NotMatchRegex(invalidRegex); + } + }; + + // Assert + act.Should().Throw() + .Which.Message.Should().Contain("is not a valid regular expression") + .And.NotContain("matches"); + } + #endregion #region Start With diff --git a/Tests/Shared.Specs/TypeAssertionSpecs.cs b/Tests/Shared.Specs/TypeAssertionSpecs.cs index c12a0f102d..f70a464d6f 100644 --- a/Tests/Shared.Specs/TypeAssertionSpecs.cs +++ b/Tests/Shared.Specs/TypeAssertionSpecs.cs @@ -1428,6 +1428,40 @@ public void When_asserting_a_selection_of_decorated_types_does_not_inherit_an_at "*because we do*attribute was found*ClassWithInheritedAttribute*"); } + [Fact] + public void When_a_selection_of_types_do_inherit_unexpected_attribute_with_the_expected_properties_it_succeeds() + { + // Arrange + var types = new TypeSelector(typeof(ClassWithInheritedAttribute)); + + // Act + Action act = () => types.Should() + .NotBeDecoratedWithOrInherit(a => ((a.Name == "Expected") && a.IsEnabled), + "because we {0}", "do"); + + // Assert + act.Should().Throw() + .WithMessage("Expected all types to not be decorated with or inherit *DummyClassAttribute*" + + " that matches ((a.Name == \"Expected\")*a.IsEnabled) because we do," + + " but a matching attribute was found on the following types:*" + + "*ClassWithInheritedAttribute*."); + } + + [Fact] + public void When_a_selection_of_types_do_not_inherit_unexpected_attribute_with_the_expected_properties_it_succeeds() + { + // Arrange + var types = new TypeSelector(typeof(ClassWithoutAttribute)); + + // Act + Action act = () => types.Should() + .NotBeDecoratedWithOrInherit(a => ((a.Name == "Expected") && a.IsEnabled), + "because we {0}", "do"); + + // Assert + act.Should().NotThrow(); + } + [Fact] public void When_injecting_a_null_predicate_into_TypeSelector_NotBeDecoratedWithOrInherit_it_should_throw() {