27

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
  • 144,921
  • 39
  • 244
  • 303
Imanol
  • 4,824
  • 2
  • 28
  • 35

4 Answers4

32

[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,691
  • 1
  • 28
  • 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
  • 32,841
  • 11
  • 75
  • 101
dimo414
  • 47,227
  • 18
  • 148
  • 244
  • 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
  • 1
    Please mind that `expectThrows` was renamed to `assertThrows`, see https://github.com/google/truth/issues/219#issuecomment-266925663. – JJD Jan 23 '23 at 12:44
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,914
  • 1
  • 60
  • 86
  • 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
0

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:

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

,

Tantallion
  • 91
  • 5