shouldly / shouldly

Should testing for .NET—the way assertions should be!

Home Page:https://docs.shouldly.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Obsolete the Should.Throw overloads that take Task instead of Func<Task>

jnm2 opened this issue · comments

(Broken out of #753)

The presence of this overload is a pit of failure when asserting exceptions like ArgumentException that mean "the caller should have known better," where the exception is not meant to be recovered from. It is common and favorable for a task-returning method to throw such exceptions directly rather than returning a task instance wrapping the exception.
(This is in contrast to exceptions intended to be recovered from like IOException, where the method should always return a faulted task rather than throwing an exception directly. Otherwise the caller must handle IOException both when the method is called and when the method is awaited, which sometimes happen at different points in the calling code.)

The pit of failure comes into play when you use the syntax Should.Throw<ArgumentException>(SomeMethod()), calling the overload that takes a Task instance. Methods that throw without returning a Task instance will cause the test to fail before Shouldly even gets involved.
The syntax is only subtly different from Should.Throw<ArgumentException>(SomeMethod) which calls the overload that takes a Func<Task> instance, which does not suffer from the inability to catch such exceptions. This nuance can easy to overlook. Presenting users with both options gives them an extra decision to make, and spending time on that decision doesn't seem valuable.

What about when you actually just have a task instance and aren't calling a method? Instead of Should.Throw<ArgumentException>(task), you'll have to do Should.Throw<ArgumentException(() => task).

Maybe we could obsolete in v4.1 and remove in v5?

Methods that throw without returning a Task instance will cause the test to fail before Shouldly even gets involved.

Exactly. Better to not offer the option that may not perform as expected, especially when the workaround for advanced use cases is simple.

I support this change.