Skip to content

Commit 18c73ce

Browse files
committed
[#94] Added possibility to do custom assertions for expected exceptions.
1 parent bc889ea commit 18c73ce

File tree

6 files changed

+166
-26
lines changed

6 files changed

+166
-26
lines changed

cute/src/main/java/io/toolisticon/cute/AnnotationProcessorWrapper.java

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
import javax.lang.model.element.PackageElement;
1313
import javax.lang.model.element.TypeElement;
1414
import javax.tools.Diagnostic;
15+
16+
import io.toolisticon.cute.CuteApi.ExceptionAssertion;
17+
import io.toolisticon.cute.CuteApi.ExceptionCheckBB;
18+
1519
import java.util.HashSet;
1620
import java.util.Set;
1721

@@ -21,7 +25,7 @@
2125
final class AnnotationProcessorWrapper implements Processor {
2226

2327
private final Processor wrappedProcessor;
24-
private final Class<? extends Throwable> expectedThrownException;
28+
private final ExceptionCheckBB exceptionChecks;
2529
private Messager messager;
2630

2731
private boolean firstRound = true;
@@ -31,9 +35,9 @@ private AnnotationProcessorWrapper(Processor processor) {
3135
this(processor, null);
3236
}
3337

34-
private AnnotationProcessorWrapper(Processor processor, Class<? extends Throwable> expectedThrownException) {
38+
private AnnotationProcessorWrapper(Processor processor, ExceptionCheckBB exceptionChecks) {
3539
this.wrappedProcessor = processor;
36-
this.expectedThrownException = expectedThrownException;
40+
this.exceptionChecks = exceptionChecks;
3741
}
3842

3943

@@ -83,19 +87,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
8387
throw (AssertionError) e;
8488
}
8589

86-
if (this.expectedThrownException != null) {
90+
if (this.exceptionChecks != null && this.exceptionChecks.getExceptionIsThrown() != null) {
8791

88-
if (!this.expectedThrownException.isAssignableFrom(e.getClass())) {
92+
if (!this.exceptionChecks.getExceptionIsThrown().isAssignableFrom(e.getClass())) {
8993
throw new FailingAssertionException(
9094
Constants.Messages.ASSERTION_GOT_UNEXPECTED_EXCEPTION_INSTEAD_OF_EXPECTED.produceMessage(
91-
this.expectedThrownException.getCanonicalName(),
95+
this.exceptionChecks.getExceptionIsThrown().getCanonicalName(),
9296
e.getClass().getCanonicalName(),
9397
e.getMessage() != null ? Constants.Messages.TOKEN__WITH_MESSAGE + e.getMessage() : "")
9498
, e);
9599
}
96100

97101
// Exception has been found
98102
expectedExceptionWasThrown = true;
103+
104+
if (this.exceptionChecks.getExceptionAssertion() != null) {
105+
doExceptionAssertion(this.exceptionChecks.getExceptionAssertion(), (Exception) e);
106+
}
99107

100108
} else {
101109

@@ -112,15 +120,20 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
112120
}
113121

114122
// check in last round if expected exception has been thrown - in this case trigger assertion error
115-
if (roundEnv.processingOver() && !expectedExceptionWasThrown && this.expectedThrownException != null) {
123+
if (roundEnv.processingOver() && !expectedExceptionWasThrown && this.exceptionChecks != null && this.exceptionChecks.getExceptionIsThrown() != null) {
116124
throw new FailingAssertionException(
117-
Constants.Messages.ASSERTION_EXPECTED_EXCEPTION_NOT_THROWN.produceMessage(this.expectedThrownException.getCanonicalName())
125+
Constants.Messages.ASSERTION_EXPECTED_EXCEPTION_NOT_THROWN.produceMessage(this.exceptionChecks.getExceptionIsThrown().getCanonicalName())
118126
);
119127
}
120128

121129

122130
return returnValue;
123131
}
132+
133+
@SuppressWarnings({ "rawtypes", "unchecked" })
134+
private <EXCEPTION extends Exception> void doExceptionAssertion(ExceptionAssertion exceptionAssertion, Exception e) {
135+
((ExceptionAssertion<EXCEPTION>)exceptionAssertion).doAssertion((EXCEPTION)e);
136+
}
124137

125138
@Override
126139
public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
@@ -139,13 +152,13 @@ public static AnnotationProcessorWrapper wrapProcessor(Processor processorToWrap
139152
return wrapProcessor(processorToWrap, null);
140153
}
141154

142-
public static AnnotationProcessorWrapper wrapProcessor(Processor processorToWrap, Class<? extends Throwable> expectedThrownException) {
155+
public static AnnotationProcessorWrapper wrapProcessor(Processor processorToWrap, ExceptionCheckBB exceptionChecks) {
143156

144157
if (processorToWrap == null) {
145158
throw new IllegalArgumentException(Constants.Messages.IAE_PASSED_PARAMETER_MUST_NOT_BE_NULL.produceMessage("processor"));
146159
}
147160

148-
return new AnnotationProcessorWrapper(processorToWrap, expectedThrownException);
161+
return new AnnotationProcessorWrapper(processorToWrap, exceptionChecks);
149162
}
150163

151164
public static <T extends Processor> AnnotationProcessorWrapper wrapProcessor(Class<T> processorTypeToWrap) {
@@ -161,18 +174,18 @@ public static <T extends Processor> AnnotationProcessorWrapper wrapProcessor(Cla
161174

162175
}
163176

164-
public static <T extends Processor> AnnotationProcessorWrapper wrapProcessor(Class<T> processorTypeToWrap, Class<? extends Throwable> expectedThrownException) {
177+
public static <T extends Processor> AnnotationProcessorWrapper wrapProcessor(Class<T> processorTypeToWrap, ExceptionCheckBB exceptionChecks) {
165178

166179
if (processorTypeToWrap == null) {
167180
throw new IllegalArgumentException(Constants.Messages.IAE_PASSED_PARAMETER_MUST_NOT_BE_NULL.produceMessage("type"));
168181
}
169182

170-
if (expectedThrownException == null) {
183+
if (exceptionChecks == null || exceptionChecks.getExceptionIsThrown() == null) {
171184
throw new IllegalArgumentException(Constants.Messages.IAE_PASSED_PARAMETER_MUST_NOT_BE_NULL.produceMessage("expected exception"));
172185
}
173186

174187
try {
175-
return new AnnotationProcessorWrapper(processorTypeToWrap.getDeclaredConstructor().newInstance(), expectedThrownException);
188+
return new AnnotationProcessorWrapper(processorTypeToWrap.getDeclaredConstructor().newInstance(), exceptionChecks);
176189
} catch (Exception e) {
177190
throw new IllegalArgumentException(Constants.Messages.IAE_CANNOT_INSTANTIATE_PROCESSOR.produceMessage(processorTypeToWrap.getCanonicalName()), e);
178191
}
@@ -258,7 +271,7 @@ static Set<AnnotationProcessorWrapper> getWrappedProcessors(CuteApi.CompilerTest
258271
}
259272

260273
if (processor != null) {
261-
wrappedProcessors.add(AnnotationProcessorWrapper.wrapProcessor(processor, compilerTestBB.getExceptionIsThrown()));
274+
wrappedProcessors.add(AnnotationProcessorWrapper.wrapProcessor(processor, compilerTestBB.getExceptionChecks()));
262275
}
263276
}
264277

@@ -268,7 +281,7 @@ static Set<AnnotationProcessorWrapper> getWrappedProcessors(CuteApi.CompilerTest
268281
try {
269282

270283
Processor processor = processorType.getDeclaredConstructor().newInstance();
271-
wrappedProcessors.add(AnnotationProcessorWrapper.wrapProcessor(processor, compilerTestBB.getExceptionIsThrown()));
284+
wrappedProcessors.add(AnnotationProcessorWrapper.wrapProcessor(processor, compilerTestBB.getExceptionChecks()));
272285

273286
} catch (Exception e) {
274287
throw new IllegalArgumentException(Constants.Messages.IAE_CANNOT_INSTANTIATE_PROCESSOR.produceMessage(processorType.getCanonicalName()));

cute/src/main/java/io/toolisticon/cute/CuteApi.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.toolisticon.fluapigen.api.FluentApiCommand;
1111
import io.toolisticon.fluapigen.api.FluentApiConverter;
1212
import io.toolisticon.fluapigen.api.FluentApiImplicitValue;
13+
import io.toolisticon.fluapigen.api.FluentApiInlineBackingBeanMapping;
1314
import io.toolisticon.fluapigen.api.FluentApiInterface;
1415
import io.toolisticon.fluapigen.api.FluentApiParentBackingBeanMapping;
1516
import io.toolisticon.fluapigen.api.FluentApiRoot;
@@ -68,8 +69,8 @@ public interface CompilerTestBB {
6869
@FluentApiBackingBeanField("compilationSucceeded")
6970
Boolean compilationSucceeded();
7071

71-
@FluentApiBackingBeanField("exceptionIsThrown")
72-
Class<? extends Exception> getExceptionIsThrown();
72+
@FluentApiBackingBeanField("exceptionChecks")
73+
ExceptionCheckBB getExceptionChecks();
7374

7475
UnitTestBase unitTest();
7576

@@ -120,6 +121,17 @@ default List<String> getNormalizedCompilerOptions() {
120121

121122

122123
}
124+
125+
@FluentApiBackingBean
126+
public interface ExceptionCheckBB {
127+
128+
@FluentApiBackingBeanField("exceptionIsThrown")
129+
Class<? extends Exception> getExceptionIsThrown();
130+
131+
@FluentApiBackingBeanField("exceptionAssertion")
132+
ExceptionAssertion<? extends Exception> getExceptionAssertion();
133+
134+
}
123135

124136

125137
@FluentApiBackingBean
@@ -926,9 +938,34 @@ public interface BlackBoxTestOutcomeInterface {
926938
* @param exception The exception to check for
927939
* @return the next fluent interface
928940
*/
929-
CompilerTestExpectAndThatInterface exceptionIsThrown(@FluentApiBackingBeanMapping(value = "exceptionIsThrown") Class<? extends Exception> exception);
930-
941+
@FluentApiInlineBackingBeanMapping(value = "exceptionChecks")
942+
CompilerTestExpectAndThatInterface exceptionIsThrown(@FluentApiBackingBeanMapping(value = "exceptionIsThrown", target = TargetBackingBean.INLINE) Class<? extends Exception> exception);
931943

944+
/**
945+
* Expect an Exception to be thrown.
946+
* This usually makes sense for unit tests rather than black box tests.
947+
* <p>
948+
* Please keep in mind that it's discouraged for processors to throw exceptions.
949+
* Please catch them in your processor and convert them to compiler messages and maybe trigger a compiler error if needed.
950+
*
951+
* @param exception The exception to check for
952+
* @return the next fluent interface
953+
*/
954+
@FluentApiInlineBackingBeanMapping(value = "exceptionChecks")
955+
<T extends Exception> CompilerTestExpectAndThatInterface exceptionIsThrown(
956+
@FluentApiBackingBeanMapping(value = "exceptionIsThrown", target = TargetBackingBean.INLINE) Class<T> exception,
957+
@FluentApiBackingBeanMapping(value = "exceptionAssertion", target = TargetBackingBean.INLINE) ExceptionAssertion<T> exceptionAssertion);
958+
959+
}
960+
961+
/**
962+
* Used to do an assertion on an expected exception.
963+
* @param <T> The exceptions type
964+
*/
965+
public interface ExceptionAssertion<T extends Exception> {
966+
967+
void doAssertion(T exception);
968+
932969
}
933970

934971
@FluentApiInterface(CompilerTestBB.class)

cute/src/test/java/io/toolisticon/cute/AnnotationProcessorWrapperTest.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.toolisticon.cute;
22

33

4+
import io.toolisticon.cute.CuteApi.ExceptionAssertion;
5+
import io.toolisticon.cute.CuteApi.ExceptionCheckBB;
46
import io.toolisticon.cute.testcases.TestAnnotationProcessor;
57
import io.toolisticon.cute.testcases.TestAnnotationProcessorWithMissingNoArgConstructor;
68
import org.hamcrest.MatcherAssert;
@@ -52,23 +54,23 @@ public void createWrapperWithType() {
5254

5355
@Test
5456
public void createWrapperWithTypeAndException() {
55-
56-
Processor unit = AnnotationProcessorWrapper.wrapProcessor(TestAnnotationProcessor.class, IllegalStateException.class);
57+
58+
Processor unit = AnnotationProcessorWrapper.wrapProcessor(TestAnnotationProcessor.class, createExceptionCheckBB(IllegalStateException.class));
5759
MatcherAssert.assertThat("Must return non null valued Processor", unit, Matchers.notNullValue());
5860

5961
}
6062

6163
@Test(expected = IllegalArgumentException.class)
6264
public void createWrapperWithTypeAndException_nullValuedProcessorClass() {
6365

64-
AnnotationProcessorWrapper.wrapProcessor((Class) null, IllegalStateException.class);
66+
AnnotationProcessorWrapper.wrapProcessor((Class) null, createExceptionCheckBB(IllegalStateException.class));
6567

6668
}
6769

6870
@Test(expected = IllegalArgumentException.class)
6971
public void createWrapperWithTypeAndException_nullValuedExceptionClass() {
7072

71-
AnnotationProcessorWrapper.wrapProcessor(TestAnnotationProcessor.class, (Class) null);
73+
AnnotationProcessorWrapper.wrapProcessor(TestAnnotationProcessor.class, createExceptionCheckBB((Class) null));
7274

7375
}
7476

@@ -87,7 +89,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
8789
@Test(expected = IllegalArgumentException.class)
8890
public void createWrapperWithTypeAndException_notIntancableProcessorClass() {
8991

90-
AnnotationProcessorWrapper.wrapProcessor(InvalidProcessor.class, IllegalStateException.class);
92+
AnnotationProcessorWrapper.wrapProcessor(InvalidProcessor.class, createExceptionCheckBB(IllegalStateException.class));
9193

9294
}
9395

@@ -250,6 +252,39 @@ public void process_testExpectedExceptionIsThrown_assertionShouldSucceed() {
250252
.executeTest();
251253

252254

255+
}
256+
257+
@Test
258+
public void process_testExpectedExceptionIsThrown_assertionShouldSucceed_withAdditionalChecks() {
259+
260+
Cute.unitTest().when(processingEnvironment -> {
261+
throw new IllegalArgumentException("XXX");
262+
})
263+
.thenExpectThat().exceptionIsThrown(IllegalArgumentException.class, (e)->{
264+
MatcherAssert.assertThat(e.getMessage(), Matchers.containsString("XXX"));
265+
})
266+
.executeTest();
267+
268+
269+
}
270+
271+
@Test
272+
public void process_testExpectedExceptionIsThrown_assertionShouldFail_becauseOfAdditionalChecks() {
273+
274+
try {
275+
Cute.unitTest().when(processingEnvironment -> {
276+
throw new IllegalArgumentException("ABC");
277+
})
278+
.thenExpectThat().exceptionIsThrown(IllegalArgumentException.class, (e)->{
279+
MatcherAssert.assertThat(e.getMessage(), Matchers.containsString("XXX"));
280+
})
281+
.executeTest();
282+
} catch (AssertionError e) {
283+
MatcherAssert.assertThat(e.getMessage(), Matchers.containsString("a string containing \"XXX\""));
284+
MatcherAssert.assertThat(e.getMessage(), Matchers.containsString("was \"ABC\""));
285+
return;
286+
}
287+
253288
}
254289

255290
@Test
@@ -313,4 +348,20 @@ public void getWrappedProcessor_testGet() {
313348
}
314349

315350

351+
private ExceptionCheckBB createExceptionCheckBB(final Class<? extends Exception> expectedExceptionType) {
352+
return new ExceptionCheckBB() {
353+
354+
@Override
355+
public Class<? extends Exception> getExceptionIsThrown() {
356+
return expectedExceptionType;
357+
}
358+
359+
@Override
360+
public ExceptionAssertion<? extends Exception> getExceptionAssertion() {
361+
// TODO Auto-generated method stub
362+
return null;
363+
}
364+
};
365+
}
366+
316367
}

cute/src/test/java/io/toolisticon/cute/CuteApiTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void test_blackbox_exceptionIsThrown() {
171171
.andSourceFiles("/compiletests/TestClass.java")
172172
.whenCompiled().thenExpectThat().exceptionIsThrown(IllegalStateException.class)
173173
).backingBean;
174-
MatcherAssert.assertThat(unit.getExceptionIsThrown(), Matchers.is(IllegalStateException.class));
174+
MatcherAssert.assertThat(unit.getExceptionChecks().getExceptionIsThrown(), Matchers.is(IllegalStateException.class));
175175

176176
}
177177

cute/src/test/java/io/toolisticon/cute/CuteTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,37 @@ public void blackBoxTest_checkForExpectedException_happyPath() {
11481148
.exceptionIsThrown(IllegalStateException.class)
11491149
.executeTest();
11501150
}
1151+
1152+
@Test
1153+
public void blackBoxTest_checkForExpectedException_happyPath_withCustomAssertion() {
1154+
Cute.blackBoxTest().given().processor(ExceptionThrowerProcessor.class)
1155+
.andSourceFiles("/compiletests/exceptionthrown/ExceptionThrownUsecase.java")
1156+
.whenCompiled()
1157+
.thenExpectThat()
1158+
.exceptionIsThrown(IllegalStateException.class, (e) -> {
1159+
MatcherAssert.assertThat(e.getMessage(), Matchers.is("WHOOPS!!!"));
1160+
})
1161+
.executeTest();
1162+
}
1163+
1164+
@Test
1165+
public void blackBoxTest_checkForExpectedException_failure_becauseOfCustomAssertion() {
1166+
try {
1167+
Cute.blackBoxTest().given().processor(ExceptionThrowerProcessor.class)
1168+
.andSourceFiles("/compiletests/exceptionthrown/ExceptionThrownUsecase.java")
1169+
.whenCompiled()
1170+
.thenExpectThat()
1171+
.exceptionIsThrown(IllegalStateException.class, (e) -> {
1172+
MatcherAssert.assertThat(e.getMessage(), Matchers.is("NOPE"));
1173+
})
1174+
.executeTest();
1175+
} catch (AssertionError e) {
1176+
MatcherAssert.assertThat(e.getMessage(), Matchers.containsString("NOPE"));
1177+
return;
1178+
}
1179+
throw new AssertionError("Expected an Assertion Error to be thrown");
1180+
}
1181+
11511182

11521183
@Test
11531184
public void blackBoxTest_checkForExpectedException_failure_otherException() {

legacy/src/main/java/io/toolisticon/cute/CompileTestBuilderApi.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public interface CompilerTestBB extends CuteApi.CompilerTestBB {
4242
}
4343

4444

45+
@FluentApiBackingBean
46+
public interface ExceptionCheckBB extends CuteApi.ExceptionCheckBB {
47+
48+
49+
}
50+
51+
4552
@FluentApiBackingBean
4653
public interface PassInConfigurationBB extends CuteApi.PassInConfigurationBB {
4754

@@ -737,7 +744,8 @@ default UnitTestBuilder useSource(String className, String content) {
737744
* @param expectedException the exceptions expected to be thrown
738745
* @return the UnitTestBuilder instance
739746
*/
740-
UnitTestBuilder expectedThrownException(@FluentApiBackingBeanMapping(value = "exceptionIsThrown") Class<? extends Exception> expectedException);
747+
@FluentApiInlineBackingBeanMapping("exceptionChecks")
748+
UnitTestBuilder expectedThrownException(@FluentApiBackingBeanMapping(value = "exceptionIsThrown", target = TargetBackingBean.INLINE) Class<? extends Exception> expectedException);
741749

742750

743751
/**

0 commit comments

Comments
 (0)