Inconsistency between StringAssertions.StartWith and String.StartsWith
See original GitHub issueDescription
StringAssertions.StartWith throws an ArgumentException when asserting that a string starts with the empty string.
This makes it so that the assertions in one of the tips are not equivalent when expectedPrefix
is empty.
Complete minimal example reproducing the issue
string actual = "abc";
string expectedPrefix = "";
actual.StartsWith(expectedPrefix).Should().BeTrue();
actual.Should().StartWith(expectedPrefix);
Expected behavior:
Both assertions to pass.
Actual behavior:
The first assertion passes, but the second throws an ArgumentException with message
Cannot compare start of string with empty string. (Parameter ‘expected’)
Versions
- Fluent Assertions: 5.10.3
- .NET runtime: .NET Core 3.1
Additional Information
StringAssertions.EndWith and String.EndsWith are inconsistent in the same way.
Issue Analytics
- State:
- Created 3 years ago
- Comments:8 (5 by maintainers)
Top Results From Across the Web
c# - Inconsistent string.StartsWith on different platforms
1 Answer 1 ... This is a known difference between Windows and Linux/Unix: on Unix platforms, nulls have no "weight". The behavior of...
Read more >Huge performance difference in string.StartsWith and ...
Very simple code: using System; using System.Diagnostics; namespace ConsoleApplication { public class Program { public static void ...
Read more >StringAssert.StartsWith Method
Tests whether the specified string begins with the specified substring and throws an exception if the test string does not start with the...
Read more >Python String | startswith() method with Examples
Python startswith() method returns either True or False. It returns True if the string starts with the prefix, otherwise False.
Read more >Java.String.startsWith()
The method startsWith() is a convenience method that checks whether a String starts with another String. We can also pass the index of...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Hence, my point above. Let’s just stick to changing
StartWith/EndWith/NotStartWith/NotEndWith
Another point that speaks in favor of this change in
StringAssertions.StartWith
/EndWith
is that it is consistent withSelfReferencingCollectionAssertions.StartWith
/EndWith
, which do allow an empty collection as prefix/suffix and always pass (as far as I can tell).I agree.
I think you meant ‘every string’ rather than ‘no string’ (or equivalently, ‘no string does
not“not start/end” with the empty string’), right? I agree with this change, because then the assertionsStartWith
andNotStartWith
are complementary (for all prefixes) in the sense that one passes and the other fails.This makes sense to me. In the same way,
StringAssertions.NotContain("")
should always fail.Counting the number of occurrences does indeed seem tricky, especially when occurrences are not allowed to overlap (which is the convention used as far as I can tell). The recursive definition that the justification for your second option uses seems clean (certainly for non-empty strings), but dealing with infinity might be problematic.
Two other options that might be worth considering are:
OccurrenceConstraint
overload by keeping theArgumentException
.s
exactlys.Length + 1
times.The second option might seem strange, but it can be justified by counting occurrences that are allowed to overlap and then noting that occurrences of the empty string cannot overlap, so the number of occurrences of the empty string should not change when we disallow overlaps.
A reasonable definition of the number of occurrences of a pattern
p
in a strings
when allowing overlaps could be the number of indicesi
(between0
ands.Length
, inclusive) for which the substrings[i..]
starting at indexi
starts withp
. If we agree that every string starts with the empty string, then plugging inp = ""
givess.Length + 1
matches.Neat properties of the
s.Length + 1
option are thats
, we have thats
occurs exactly once in itself, and thatbut it does not seem very intuitive.