How to test that an expected exception is being thrown using google-truth?
Asked Answered
F

4

28

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?

Frog answered 18/7, 2016 at 10:47 Comment(0)
B
34

[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.

Breedlove answered 10/8, 2016 at 7:4 Comment(5)
What class/package is fail() in?Disassemble
What about assertThrows in junit5? I have been using this with google-truth and it seems to work really well.Yaroslavl
That's what google is primarily using, through the back-port of the feature to junit4.Breedlove
Answer updated to reflect that the team now basically recommends assertThrows()Breedlove
tir38: fail creates and throws an AssertionFailedError, which is part of JUnit, so the dependency remains.Githens
X
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");
Xanthein answered 6/6, 2017 at 9:0 Comment(2)
I updated my answer to reflect this information. I believe the expectThrows has been renamed to assertThrows, but this is basically correct.Breedlove
Please mind that expectThrows was renamed to assertThrows, see github.com/google/truth/issues/219#issuecomment-266925663.Rouble
D
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());
                }
            }
        }
    }
}
Decurved answered 19/7, 2016 at 18:42 Comment(1)
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");Breedlove
G
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>");
}

,

Githens answered 7/6, 2023 at 18:50 Comment(1)
Why would depending on JUnit be a problem if you already depend on Truth? It's more robust than reinventing a square wheel.Naji

© 2022 - 2024 — McMap. All rights reserved.