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.containsString;
021import static org.hamcrest.Matchers.is;
022import static org.hamcrest.Matchers.isA;
023import static org.hamcrest.Matchers.notNullValue;
024import static org.hamcrest.Matchers.nullValue;
025import static org.junit.Assert.assertThat;
026import static tech.sirwellington.alchemy.test.Checks.Internal.checkNotNull;
027
028/**
029 * Makes it easier syntactically using Java 8 to assert an Exception is thrown by a section of code.
030 * You can also perform additional verification on the exception that is thrown.
031 *
032 * Example:
033 *
034 * <pre>
035 * {@code
036 * assertThrows(() -> someFunctionThatThrows())
037 * .isIntanceOf(RuntimeException.class)
038 * .hasNoCause();
039 * }
040 * </pre>
041 *
042 * @author SirWellington
043 */
044@FluidAPIDesign
045public final class ThrowableAssertion
046{
047
048    /**
049     * Assert that a function throws an exception.
050     *
051     * @param operation The Lambda function that encapsulates code you expect to throw an exception.
052     *
053     * @return
054     *
055     * @throws ExceptionNotThrownException If no exception is thrown.
056     */
057    public static ThrowableAssertion assertThrows(ExceptionOperation operation) throws ExceptionNotThrownException
058    {
059        checkNotNull(operation, "missing operation");
060        return new ThrowableAssertion(operation)
061                .execute();
062    }
063
064    private Throwable caught;
065    private final ExceptionOperation operation;
066
067    private ThrowableAssertion(ExceptionOperation operation)
068    {
069        this.operation = operation;
070    }
071
072    private ThrowableAssertion execute() throws ExceptionNotThrownException
073    {
074        try
075        {
076            operation.call();
077        }
078        catch (Throwable ex)
079        {
080            this.caught = ex;
081            return this;
082        }
083        throw new ExceptionNotThrownException("Expected an exception");
084    }
085
086    /**
087     * Check that the Exception is of a particular type.
088     *
089     * @param exceptionClass The expected type of the Exception.
090     *
091     * @return
092     */
093    public ThrowableAssertion isInstanceOf(Class<? extends Throwable> exceptionClass)
094    {
095        assertThat(caught, isA((Class<Throwable>) exceptionClass));
096        return this;
097    }
098
099    /**
100     * Checks to make sure the exception contains a certain message.
101     *
102     * @param expectedMessage The exact message expected
103     *
104     * @return
105     */
106    public ThrowableAssertion hasMessage(String expectedMessage)
107    {
108        assertThat(caught.getMessage(), is(expectedMessage));
109        return this;
110    }
111
112    /**
113     * Assert that the exception contains a string in its message.
114     *
115     * @param messageString The partial message to expected.
116     *
117     * @return
118     */
119    public ThrowableAssertion containsInMessage(String messageString)
120    {
121        assertThat(caught.getMessage(), containsString(messageString));
122        return this;
123    }
124
125    /**
126     * Assert that the exception has no causing exception
127     *
128     * @return
129     */
130    public ThrowableAssertion hasNoCause()
131    {
132        assertThat(caught.getCause(), nullValue());
133        return this;
134    }
135
136    /**
137     * Asserts that the Exception has a cause of a particular type.
138     *
139     * @param exceptionClass The type expected.
140     *
141     * @return
142     */
143    public ThrowableAssertion hasCauseInstanceOf(Class<? extends Throwable> exceptionClass)
144    {
145        assertThat(caught.getCause(), notNullValue());
146        assertThat(caught.getCause(), isA((Class<Throwable>) exceptionClass));
147        return this;
148    }
149}