google / truth

Fluent assertions for Java and Android
https://truth.dev/
Apache License 2.0
2.72k stars 259 forks source link

Include `assertThrows` into core api #621

Open changusmc opened 5 years ago

changusmc commented 5 years ago

We'd like to use this code https://github.com/aosp-mirror/platform_frameworks_support/commit/bf04c31bd91bc6188abef45f7d9c1fa4040e5633#diff-46afb720089a0149afb46f71274f5642 from our Kotlin tests, can you please include it into Truth?

We could just add it to one of our internal libraries and use it. However, seeing as this code is being duplicated on many repos on GitHub: https://github.com/search?l=Kotlin&p=2&q=ThrowableSubject&type=Code, it might make sense to include it in Truth.

cgruber commented 5 years ago

Just piping up to point out that this is slightly different than previous discussions about assertThrows() insofar as (a) 4.13 isn't released, despite it having betas and being internally used in Google, so not everyone will have assertThrows from JUnit handy in their deps, (b) I've now seen several cases of people including a dep on kotlin.test exclusively for assertFailsWith<SomeErrorType> { lambda logic } while using Truth for everything else.

While I think it kind of made sense before when we were certain everyone would have this (or equivalent) function in their deps, I don't think that's a sound assumption under present conditions.

(Trying to head off objections that I've already heard inside Google to related inquiries, since I think we're in a different situation than that in which those discussions occurred)

kluever commented 5 years ago

/cc @cushon @cpovirk @lowasser

cushon commented 5 years ago

related: b/114409026

JakeWharton commented 5 years ago

The linked API is notably different (and more usable, not that I'm biased as its author) than JUnit and kotlin.test.

In Kotlin, for JUnit, you have to use assertThrows(IllegalStateException::class.java) { .. } whereas this function requires only assertThrows<IllegalStateException> { .. }. Having to specify ::class.java isn't going to kill anyone, but it's visual clutter. kotlin.test gets to share the same nice syntax.

Beyond that, the other APIs immediately fall short. JUnit lets you assert on the exact message but nothing like start/end/regex-based matching. For that, you need to change how you assert by capturing the return value and asserting on the message or wrapping in a ThrowableSubject and then asserting.

Similarly, any causal analysis requires capturing the Throwable, wrapping, and then analyzing.

The above snippet returns the ThrowableSubject directly which lets you immediately chain into any of these more advanced assertions, or combine them in an apply block.

assertThrows<IllegalStateException> {
  ...
}.apply {
  hasMessage().startsWith("Whatever: ")
  hasCause().isInstanceOf(SomeException::class.java)
}
cgruber commented 5 years ago

I really do like @JakeWharton's variant, but I'm not sure if this can be achieved from within Java, so there are a few things questions this forces.

  1. is it time to start letting kotlin into Truth.
    1. if so, is it reasonable to make two flavors, one of which uses reified types and the other of which passes a class object, so Java users can still use it?
  2. if not, is it reasonable to make a truth-kotlin extension project?
    1. if so then should we put a Java variant that takes Class like assertThrows(Class) in the main project?

Personally I'd love to see Jake's flavor, but we also have a lot of Java left.

Marcono1234 commented 1 year ago

Is this issue here about Kotlin only? From the title of this issue it sounds to be generic, but the comments here seem to be mostly / only about Kotlin.

Even for Java projects a Truth assertThrows method would be useful:

Maybe it would also be useful to have two flavors: One assertThrows which checks if the exception class is equal, and another one which checks if the class is an instanceof.

mrj commented 1 year ago

Yes, I'm not keen to add a JUnit dependency just for assertThrows, especially since I'm making my Jar distribution self-test when run, so the testing frameworks are inside it.

I'm currently using (Java):

try {
    [Manipulate object "o"]
    assertWithMessage("Manipulating %s should have thrown an UnsupportedOperationException", o).fail();
} catch (UnsupportedOperationException expected) {}