Skip to content
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

Deprecate expectThrows in favor of assertThrows returning the exception #531

Closed
cpovirk opened this issue Sep 30, 2016 · 25 comments
Closed

Comments

@cpovirk
Copy link
Contributor

cpovirk commented Sep 30, 2016

Is it possible to rename expectThrows?

As we introduce people to expectThrows, we keep hearing:

  • (almost always) This is great.
  • (occasionally) This isn't what "expect" usually means.

I (and the people I'm hearing from) seem to be used to to "expect" as meaning: "If this fails, record the failure in a list. When the test fails, show all the failures." For example: https://github.com/google/googletest/blob/master/googletest/docs/Primer.md#assertions

Additionally, the "assert" vs. "expect" naming doesn't seem suggest "one of these returns the exception, and the other doesn't" to us.

I don't know if this is still open to discussion, but if it is, my first attempt would be...

    SomeException caught =
        exceptionThrownBy(SomeException.class, () -> { /* code */ });

...since it returns the exception thrown by the code. I'm happy to fish around for suggestions from others if it would be worthwhile, but I figured I should check before putting significant time into this. Thanks.

@smoyer64
Copy link
Contributor

smoyer64 commented Oct 1, 2016

The @rule available in JUnit 4.12 is called ExpectedException. I guess I never really thought about the method name "expectThrows" since it's so close.

@sbrannen
Copy link
Member

sbrannen commented Oct 1, 2016

TBH, I've personally never been a fan of the expectThrows method in the Assertions class.

Aside from the two fail methods in Assertions, expectThrows is the only method not named assert*.

Proposal

If it were up to me, I would simply delete the existing assertThrows method and rename expectThrows to assertThrows.

Rationale:

  1. Assertions should assert something.
  2. We don't need two methods that basically do the same thing. (note that assertThrows delegates to expectThrows anyway).
  3. If users want to do something with the caught exception, they can; otherwise, they can simply ignore the return value.
  4. The confusion that users are confronted with due to the presence of assertThrows and expectThrows is unwarranted.

@junit-team/junit-lambda, what do the rest of you think about my proposal?

@mlevison
Copy link

mlevison commented Oct 1, 2016

Sam - Not a team member, but as a user and someone who educates others this
an nice tidy up. One fewer exceptions to the rule. Less cognitive load.

Cheers
Mark just off a red eye

On Oct 1, 2016 9:28 AM, "Sam Brannen" notifications@github.com wrote:

TBH, I've personally never been a fan of the expectThrows method in the
Assertions class.

Aside from the two fail methods in Assertions, expectThrows is the only
method not named assert*.
Proposal

If it were up to me, I would simply delete the existing assertThrows
method and rename expectThrows to assertThrows.

Rationale:

  1. Assertions should assert something.
  2. We don't need two methods that basically do the same thing. (note
    that assertThrows delegates to expectThrows anyway).
  3. If users want to do something with the caught exception, they can;
    otherwise, they can simply ignore the return value.
  4. The confusion that users are confronted with due to the presence of
    assertThrows and expectThrows is unwarranted.

@junit-team/junit-lambda
https://github.com/orgs/junit-team/teams/junit-lambda, what do the rest
of you think about my proposal?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#531 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAImZlfj6cOswSUHjOfzI6veppwhljQjks5qvl_rgaJpZM4KLiHM
.

@bechte
Copy link
Contributor

bechte commented Oct 1, 2016

I have also been stumbled upon those methods being confused. Totally agree. 👍

@marcphilipp
Copy link
Member

I only remember there being some argument about some static analysis tools (I don't remember which) that would give you warnings when calling a method that returns an exception which you don't consume. IIRC that was the reason a void variant was introduced and, since overloading was not an option, a different name was used for the method that is now called expectThrows probably to sound similiar to ExpectedException. At the time that seemed like a reasonable compromise to me. However, I can see the benefit in @sbrannen's proposal so I wouldn't veto if we decide to follow it.

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

Thanks for chiming in guys.

@marcphilipp, yep, I believe you are correct that the second method was added in order to avoid warnings, but I also don't recall which IDE/tool emits warnings for an used/unassigned return value.

@mmerdes, do you also agree with my proposal?

@jbduncan
Copy link
Contributor

jbduncan commented Oct 3, 2016

@sbrannen, I know that IntelliJ and Google's error-prone compiler emit warnings if the return values of methods annotated with @CheckReturnValue are not assigned to a variable.

Might one of both of these be the offender(s) that prompted the existence of expectThrows?

@sormuras
Copy link
Member

sormuras commented Oct 3, 2016

IntelliJ reports the following warning: Result of 'expectThrows()' not thrown -- I just remove the check mark here.

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

@sormuras, thanks for sharing the screenshot.

I'm guessing there's a way to disable that in IntelliJ as well -- yes?

I'm also guessing that IntelliJ IDEA could be improved to not report a warning for such methods in assertion utilities. 😉

@akozlova, what are your thoughts?

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

@jbduncan, it looks like Goggle's error-prone will address that here: google/error-prone#452

@sormuras
Copy link
Member

sormuras commented Oct 3, 2016

I'm guessing there's a way to disable that in IntelliJ as well -- yes?

Yes. See second shot linked above.

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

For anyone interested, the source of expectThrows can be seen here:

@cpovirk
Copy link
Contributor Author

cpovirk commented Oct 3, 2016

I know that IntelliJ and Google's error-prone compiler emit warnings if the return values of methods annotated with @CheckReturnValue are not assigned to a variable.

Google's Error Prone cares only about methods with @CheckReturnValue, not with other methods that return Throwable (in contrast, it appears, to IntelliJ IDEA).

It looks like Goggle's error-prone will address that here: google/error-prone#452

That bug report was to address a slightly different problem:

IndexOutOfBoundsException thrown = expectThrows(
    IndexOutOfBoundsException.class,
    () -> {
      list.get(99); // problem reported here
    });

The idea is that a method like List.get would be annotated @CheckReturnValue, so Error Prone would produce an error for its unused return value. The bug report requested that we disable @CheckReturnValue checking for the last statement inside the ThrowingRunnable method/lambda. (And Error Prone has made the change, though I doubt it's in a release yet.)

Error Prone already doesn't mind if you ignore the return value of expectThrows. So this is fine:

expectThrows(
    UnsupportedOperationException.class,
    () -> {
      list.clear();
    });

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

Hi @cpovirk,

Thanks for the clarification!

@sbrannen
Copy link
Member

sbrannen commented Oct 3, 2016

@cpovirk, please note that ThrowingRunnable is from JUnit 4; whereas, JUnit Jupiter (i.e., the programming model for JUnit 5) has an Executable functional interface which serves the same purpose (just with a more generic name). So if you're explicitly supporting ThrowingRunnable, you'll want to add explicit support for Executable as well. 😉

@cpovirk
Copy link
Contributor Author

cpovirk commented Oct 3, 2016

@sbrannen Thanks, I didn't realize that. It looks like the person who made the fix did, and he's keying off the method: org.junit.Assert/org.junit.jupiter.api.Assertions assertThrows/expectThrows. So I think we're in good shape.

google/error-prone@f9ea34b#diff-cf2cabfe009902419c7dde78f91abe3aR243

@mmerdes
Copy link
Contributor

mmerdes commented Oct 4, 2016

@sbrannen
yes, sounds reasonable to me

@sbrannen sbrannen changed the title Possible to rename expectThrows? Deprecate expectThrows in favor of assertThrows returning the exception Oct 4, 2016
@sbrannen
Copy link
Member

sbrannen commented Oct 4, 2016

FYI: expectThrows is now deprecated and will be removed in 5.0 M4.

@kcooney
Copy link
Member

kcooney commented Nov 24, 2016

@sbrannen I just found this thread. Shouldn't we make the same change in JUnit 4.13? We haven't started the release process for 4.13.

Did we decide that IntelliJ users should simply disable the warning, or did the warning only happen if the method was annotated with @CheckReturnValue?

@sbrannen
Copy link
Member

Shouldn't we make the same change in JUnit 4.13? We haven't started the release process for 4.13.

Yes, I would recommend making the same change in JUnit 4.13.

Did we decide that IntelliJ users should simply disable the warning, or did the warning only happen if the method was annotated with @CheckReturnValue?

Google error-prone already has it covered (see link by @cpovirk), and IntelliJ users can simply disable that warning if it bothers them (see attached screenshot above in this thread).

@sormuras
Copy link
Member

The screenshot is here:
IDEA

sbrannen pushed a commit that referenced this issue Dec 12, 2016
Changes made in #531 introduced a regression. Namely, the
method signature for `expectThrows()` was not completely
copied to `assertThrows()` with regard to generic types.

This commit restores the original signature for `expectThrows()`
in `assertThrows()`.

Issue: #599
@kcooney
Copy link
Member

kcooney commented Dec 12, 2016

FYI: there is some discussion at junit-team/junit4#1396 about whether assertThrows() is a good name considering the method returns something. Having the name be different than the other methods might reduce confusion (Edit: i.e. having the name not start with "assert" could give a hint that it's a different kind of method)

@sbrannen
Copy link
Member

@kcooney, we have already hashed this out and implemented what we decided on.

Basically, this is one of those areas of API design for which you'll only please half of the audience. Some people love that there is only a single assertion method for exceptions. Others (as in the JUnit 4 thread) dislike it severely.

As for the name, "expect" is definitively no better than "assert" or "verify" or "check". They all mean the same thing "assert an expectation"; none of them implies that a value is returned. The only way to convey without a doubt that an assertion method returns something is to state that explicitly in the method name -- for example, assertThatCodeBlockThrowsExceptionAndReturnExceptionThrown(...). Even if we shorten that to something like assertExceptionThrownAndReturnIt(...), that is still exceedingly verbose in contrast to the rest of the assertion methods.

That's why we opted for assertThrows(...). It's clear and concise. The fact that it breaks from JUnit's tradition by returning a value is well documented, and people will catch on immediately, especially since every example will show it in use.

And... as always -- and as explicitly stated in the JUnit 5 User Guide -- nobody is forced to use JUnit's assertions: people are free to use AssertJ, Hamcrest, Truth, etc.

@mlevison
Copy link

mlevison commented Dec 14, 2016 via email

@marcphilipp
Copy link
Member

That's why we opted for assertThrows(...). It's clear and concise. The fact that it breaks from JUnit's tradition by returning a value is well documented, and people will catch on immediately, especially since every example will show it in use.

I totally agree with @sbrannen on this one.

bazel-io pushed a commit to bazelbuild/bazel that referenced this issue Feb 24, 2018
More information: junit-team/junit5#531

PiperOrigin-RevId: 186900384
jianglai pushed a commit to google/nomulus that referenced this issue Mar 7, 2018
More information: junit-team/junit5#531

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=187034408
luca-digrazia pushed a commit to luca-digrazia/DatasetCommitsDiffSearch that referenced this issue Sep 4, 2022
    More information: junit-team/junit5#531

    PiperOrigin-RevId: 186900384
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants