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