-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ArgumentException raising ArgumentNullException on ThrowIfNullOrEmpty #111028
Comments
This is already the case.
This is a loose coding guideline that’s violated all the time. Any time you see factory Personally I was hesitant to use |
How
If you are going to test something, you can't just make tests based off your assumptions and intuitions. You have to test against the defined/documented behavior. This is what the documentation for
There are no workarounds necessary, just stick to the documented behavior when writing your tests. Therefore, the correct expectation to begin with is that your 2nd test case has to fail as it is supposed to. |
The issue isn't the fact that an ArgumentNullException is raised for a #null vs. an ArgumentException being raised for an empty string. When the guard methods were introduced on ArgumentNullException.ThrowIfNull() I was originally wishing that it would have covered "OrEmpty" strings as well, but was happy with it being covered under ArgumentException where it belongs. Though I do get it, ArgumentNullException "is-a" ArgumentException, so returning one itself isn't a bug. Part of the issue is NUnit's behaviour as it is a bit disappointing that My test doesn't care whether the scenario raises a specific exception in each case, more-so that an exception is bubbled. This is a rare check in a unit test as most public behaviour I am testing handles exceptions and returns a particular result. This comes up more when I have written more low-level functionality that I want specific tests covering that behaviour rather than relying on just "touches" from higher level tests. This would have be a completely non-issue if the guards were in fact treated using a factory-like class rather than factory methods. For instance if I have ArgumentGuard.ThrowIfNull() resulting in a thrown ArgumentNullException vs ArgumentGuard.ThrowIfNullOrEmpty() throwing an ArgumentNullException or ArgumentException as suitable. A class like ArgumentGuard is serving as an abstract factory (not-factory, "thrower?") class. The issue/smell I was raising is having factory-like methods as part of a specific type that are handing off and returning different types. In object instantiation land I might create an AnimalFactory class to create Bears, Elk, and Eagles etc. but I don't believe it would be good practice to have factory methods creating sub-classes in the Animal base class. Edit: Fine to have this closed as a non-issue / as-designed given the test assertion workaround /w Assert.Catch works just fine. |
Modern NUnit syntax encourages you to use the constraint syntax, with the |
Tagging subscribers to this area: @dotnet/area-system-runtime |
If I remember correctly, such concepts were discussed as part of exposing the In general the BCL tends to not have such separate factory classes and in places that they have been defined/exposed in the past, we've often found them to be too limiting for long term versioning. -- This is notably also why There are dozens to hundreds of design patterns, and for every design pattern that says "x is a code smell" there is an alternative pattern that says its the right thing and that y is the code smell instead. The BCL typically uses the Framework Design Guidelines (https://www.amazon.com/Framework-Design-Guidelines-Conventions-Addison-Wesley/dp/0135896460 -- of which a limited and slightly outdated high level overview is https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/). This book comes from decades of combined design experience in .NET and other languages from the API review team and other key .NET team members (past and present). The general adherence to it and decisions that are based on it are part of what has allowed .NET to stand the test of time and have so few breaking changes and almost no redesigns for its core. It's what has allowed code written and types designed 20 years ago to still be just as applicable today in most scenarios. |
Description
This seems to be a break in inheritance where a base class should never be aware of its subclasses. The ThrowNullOrEmptyException method in ArgumentException is handing off to a static method in the ArgumentNullException subclass which results in the ArgumentNullException being raised. When I am using the ArgumentException.ThrowIfNullOrEmpty() guard and I would expect an ArgumentException if that guard trips.
I can see why it was written that way, but if I had an Animal base class and a Bear, an Elk, and an Eagle subclass extending Animal, it would be very weird to have a static method in Animal calling something in just Bear. You wouldn't do it in within the scope of an "as Animal" instance, and it does not seem right to do it in the static scope either.
Reproduction Steps
As a very base example:
Expected behavior
ArgumentException raised in both cases.
Actual behavior
The first test case passes, the second fails receiving ArgumentNullException instead of ArgumentException.
Regression?
I don't believe a regression, ThrowIfNullOrEmpty guards were added in .Net Core. The methods to hand off to the subclass ArgumentNullException was added 2-3 years ago.
Known Workarounds
It is an exception guard so not a breaking issue, the unit tests that might be asserting the guards need to handle the fact that two different exceptions could be returned for the same guard.
Configuration
.Net Core 9, Windows 10, x64. Not specific to this configuration.
Other information
No response
The text was updated successfully, but these errors were encountered: