23

I just want to test if an exception with a given message is being thrown using google-truth.

Is quite easy to do that using junit using @Test(expected=, but I'm unable to figure out how to do that with truth. There are no samples around ThrowableSubject.

Should I stick with plain JUnit for these kind of tests?

Naftali
  • 142,114
  • 39
  • 237
  • 299
Imanol
  • 4,744
  • 2
  • 25
  • 35

3 Answers3

28

[updated]

The Truth authors recommend using JUnit 4.13/5's assertThrows() mechanism, since this doesn't really need support in Truth. This would look more like:

SpecificException e = 
    assertThrows(SpecificException.class, () -> doSomethingThatThrows());
assertThat(e).hasMessageThat().contains("blah blah blah");
assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
assertThat(e).hasCauseThat().hasMessageThat().contains("blah");

This is recommended over try/fail/catch as it is terser, avoids the "missing fail" problem, and returns an object that can be asserted-on using the ThrowableSubject in Truth.

If you do not have assertThrows(), then please use the try/fail/catch pattern, as this is clear and explicit.

try {
  doSomethingThatThrows(); 
  fail("method should throw");
} catch (SpecificException e) {
  // ensure that e was thrown from the right code-path
  // especially important if it's something as frequent
  // as an IllegalArgumentException, etc.
  assertThat(e).hasMessage("blah blah blah");
}

While @Rule ExpectedException and @Test(exception=...) exist in JUnit, these aren't recommended by the Truth team, insofar as they have some subtle (and less subtle) ways you can write tests that pass but which should fail.

While this is also true of try/fail/catch, internally Google mitigates this with the use of error-prone, which provides a static compile-time check to ensure that this pattern doesn't omit the fail(), etc. It is highly recommended that you use error-prone or another static analysis check to catch these. Sadly, the rule-based and annotation-based methods aren't as easily amenable to static analysis as this try/catch block.

Christian Gruber
  • 4,581
  • 1
  • 27
  • 28
4

As an update here, we've moved away from the pattern Christian described, and Issue #219 has been closed in favor of JUnit's expectThrows() (coming in 4.13, similar methods already exists in TestNG's Assert).

In tandem with expectThrows() you can use Truth to make assertions about the thrown exception. So Christian's example would now be:

SpecificException expected = expectThrows(
    SpecificException.class, () -> doSomethingThatThrows());
assertThat(expected).hasMessageThat().contains("blah blah blah");
Nicole
  • 31,900
  • 11
  • 72
  • 98
dimo414
  • 44,897
  • 17
  • 143
  • 228
  • 1
    I updated my answer to reflect this information. I believe the expectThrows has been renamed to assertThrows, but this is basically correct. – Christian Gruber Nov 07 '18 at 18:28
2

There is currently no built-in way to verify an expected Exception with google-truth. You can do one of the following:

I believe google-truth does not have any similar functionality because it supports Java 1.6.

import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.common.truth.SubjectFactory;
import org.junit.Test;

import java.util.concurrent.Callable;

import static com.google.common.truth.Truth.assertAbout;

public class MathTest {
    @Test
    public void addExact_throws_ArithmeticException_upon_overflow() {
        assertAbout(callable("addExact"))
            .that(() -> Math.addExact(Integer.MAX_VALUE, 1))
            .willThrow(ArithmeticException.class);
    }

    static <T> SubjectFactory<CallableSubject<T>, Callable<T>> callable(String displaySubject) {
        return new SubjectFactory<CallableSubject<T>, Callable<T>>() {
            @Override public CallableSubject<T> getSubject(FailureStrategy fs, Callable<T> that) {
                return new CallableSubject<>(fs, that, displaySubject);
            }
        };
    }

    static class CallableSubject<T> extends Subject<CallableSubject<T>, Callable<T>> {
        private final String displaySubject;

        CallableSubject(FailureStrategy failureStrategy, Callable<T> callable, String displaySubject) {
            super(failureStrategy, callable);
            this.displaySubject = displaySubject;
        }

        @Override protected String getDisplaySubject() {
            return displaySubject;
        }

        void willThrow(Class<?> clazz) {
            try {
                getSubject().call();
                fail("throws a", clazz.getName());
            } catch (Exception e) {
                if (!clazz.isInstance(e)) {
                    failWithBadResults("throws a", clazz.getName(), "throws a", e.getClass().getName());
                }
            }
        }
    }
}
Community
  • 1
  • 1
heenenee
  • 19,367
  • 1
  • 54
  • 83
  • We're looking at something more robust for Java8, which will such less. Something along the lines of `assertThat(() -> runMethod()).throws(Throwable.class).withMessage("blah");` – Christian Gruber Aug 10 '16 at 06:35