001/*
002 * Copyright 2015 SirWellington Tech.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package tech.sirwellington.alchemy.test.junit;
017
018import tech.sirwellington.alchemy.annotations.designs.FluidAPIDesign;
019
020import static org.hamcrest.Matchers.*;
021import static org.junit.Assert.assertThat;
022import static tech.sirwellington.alchemy.test.Checks.Internal.checkNotNull;
023
024/**
025 * Makes it easier syntactically using Java 8 to assert an Exception is thrown by a section of code.
026 * You can also perform additional verification on the exception that is thrown.
027 * <p>
028 * Example:
029 * <p>
030 * <pre>
031 * {@code
032 * assertThrows(() -> someFunctionThatThrows())
033 * .isIntanceOf(RuntimeException.class)
034 * .hasNoCause();
035 * }
036 * </pre>
037 *
038 * @author SirWellington
039 */
040@FluidAPIDesign
041public final class ThrowableAssertion
042{
043
044    /**
045     * Assert that a function throws an exception.
046     *
047     * @param operation The Lambda function that encapsulates code you expect to throw an exception.
048     * @return
049     * @throws ExceptionNotThrownException If no exception is thrown.
050     */
051    public static ThrowableAssertion assertThrows(ExceptionOperation operation) throws ExceptionNotThrownException
052    {
053        checkNotNull(operation, "missing operation");
054        return new ThrowableAssertion(operation)
055                .execute();
056    }
057
058    private Throwable caught;
059    private final ExceptionOperation operation;
060
061    private ThrowableAssertion(ExceptionOperation operation)
062    {
063        this.operation = operation;
064    }
065
066    private ThrowableAssertion execute() throws ExceptionNotThrownException
067    {
068        try
069        {
070            operation.call();
071        }
072        catch (Throwable ex)
073        {
074            this.caught = ex;
075            return this;
076        }
077        throw new ExceptionNotThrownException("Expected an exception");
078    }
079
080    /**
081     * Check that the Exception is of a particular type.
082     *
083     * @param exceptionClass The expected type of the Exception.
084     * @return
085     */
086    public ThrowableAssertion isInstanceOf(Class<? extends Throwable> exceptionClass)
087    {
088        assertThat(caught, isA((Class<Throwable>) exceptionClass));
089        return this;
090    }
091
092    /**
093     * Checks to make sure the exception contains a certain message.
094     *
095     * @param expectedMessage The exact message expected
096     * @return
097     */
098    public ThrowableAssertion hasMessage(String expectedMessage)
099    {
100        assertThat(caught.getMessage(), is(expectedMessage));
101        return this;
102    }
103
104    /**
105     * Assert that the exception contains a string in its message.
106     *
107     * @param messageString The partial message to expected.
108     * @return
109     */
110    public ThrowableAssertion containsInMessage(String messageString)
111    {
112        assertThat(caught.getMessage(), containsString(messageString));
113        return this;
114    }
115
116    /**
117     * Assert that the exception has no causing exception
118     *
119     * @return
120     */
121    public ThrowableAssertion hasNoCause()
122    {
123        assertThat(caught.getCause(), nullValue());
124        return this;
125    }
126
127    /**
128     * Asserts that the Exception has a cause of a particular type.
129     *
130     * @param exceptionClass The type expected.
131     * @return
132     */
133    public ThrowableAssertion hasCauseInstanceOf(Class<? extends Throwable> exceptionClass)
134    {
135        assertThat(caught.getCause(), notNullValue());
136        assertThat(caught.getCause(), isA((Class<Throwable>) exceptionClass));
137        return this;
138    }
139}