Skip to content

Commit 09c64d5

Browse files
committed
Improve ApplicationContextRunner diagnostics when context start fails
Closes gh-10250
1 parent 114d270 commit 09c64d5

File tree

2 files changed

+150
-81
lines changed

2 files changed

+150
-81
lines changed

spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
package org.springframework.boot.test.context.assertj;
1818

19+
import java.io.BufferedReader;
20+
import java.io.IOException;
21+
import java.io.PrintWriter;
22+
import java.io.StringReader;
23+
import java.io.StringWriter;
24+
1925
import org.assertj.core.api.AbstractAssert;
2026
import org.assertj.core.api.AbstractObjectArrayAssert;
2127
import org.assertj.core.api.AbstractObjectAssert;
@@ -37,6 +43,7 @@
3743
*
3844
* @param <C> The application context type
3945
* @author Phillip Webb
46+
* @author Andy Wilkinson
4047
* @since 2.0.0
4148
* @see ApplicationContextRunner
4249
* @see AssertableApplicationContext
@@ -70,9 +77,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
7077
*/
7178
public ApplicationContextAssert<C> hasBean(String name) {
7279
if (this.startupFailure != null) {
73-
throwAssertionError(new BasicErrorMessageFactory(
74-
"%nExpecting:%n <%s>%nto have bean named:%n <%s>%nbut context failed to start",
75-
getApplicationContext(), name));
80+
throwAssertionError(contextFailedToStartWhenExpecting(
81+
"to have bean named:%n <%s>", name));
7682
}
7783
if (findBean(name) == null) {
7884
throwAssertionError(new BasicErrorMessageFactory(
@@ -96,9 +102,8 @@ public ApplicationContextAssert<C> hasBean(String name) {
96102
*/
97103
public ApplicationContextAssert<C> hasSingleBean(Class<?> type) {
98104
if (this.startupFailure != null) {
99-
throwAssertionError(new BasicErrorMessageFactory(
100-
"%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut context failed to start",
101-
getApplicationContext(), type));
105+
throwAssertionError(contextFailedToStartWhenExpecting(
106+
"to have a single bean of type:%n <%s>", type));
102107
}
103108
String[] names = getApplicationContext().getBeanNamesForType(type);
104109
if (names.length == 0) {
@@ -127,9 +132,8 @@ public ApplicationContextAssert<C> hasSingleBean(Class<?> type) {
127132
*/
128133
public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type) {
129134
if (this.startupFailure != null) {
130-
throwAssertionError(new BasicErrorMessageFactory(
131-
"%nExpecting:%n <%s>%nnot to have any beans of type:%n <%s>%nbut context failed to start",
132-
getApplicationContext(), type));
135+
throwAssertionError(contextFailedToStartWhenExpecting(
136+
"not to have any beans of type:%n <%s>", type));
133137
}
134138
String[] names = getApplicationContext().getBeanNamesForType(type);
135139
if (names.length > 0) {
@@ -153,9 +157,8 @@ public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type) {
153157
*/
154158
public ApplicationContextAssert<C> doesNotHaveBean(String name) {
155159
if (this.startupFailure != null) {
156-
throwAssertionError(new BasicErrorMessageFactory(
157-
"%nExpecting:%n <%s>%nnot to have any beans of name:%n <%s>%nbut context failed to start",
158-
getApplicationContext(), name));
160+
throwAssertionError(contextFailedToStartWhenExpecting(
161+
"not to have any beans of name:%n <%s>", name));
159162
}
160163
try {
161164
Object bean = getApplicationContext().getBean(name);
@@ -181,9 +184,8 @@ public ApplicationContextAssert<C> doesNotHaveBean(String name) {
181184
*/
182185
public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) {
183186
if (this.startupFailure != null) {
184-
throwAssertionError(new BasicErrorMessageFactory(
185-
"%nExpecting:%n <%s>%nto get beans names with type:%n <%s>%nbut context failed to start",
186-
getApplicationContext(), type));
187+
throwAssertionError(contextFailedToStartWhenExpecting(
188+
"to get beans names with type:%n <%s>", type));
187189
}
188190
return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type))
189191
.as("Bean names of type <%s> from <%s>", type, getApplicationContext());
@@ -207,9 +209,8 @@ public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) {
207209
*/
208210
public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) {
209211
if (this.startupFailure != null) {
210-
throwAssertionError(new BasicErrorMessageFactory(
211-
"%nExpecting:%n <%s>%nto contain bean of type:%n <%s>%nbut context failed to start",
212-
getApplicationContext(), type));
212+
throwAssertionError(contextFailedToStartWhenExpecting(
213+
"to contain bean of type:%n <%s>", type));
213214
}
214215
String[] names = getApplicationContext().getBeanNamesForType(type);
215216
if (names.length > 1) {
@@ -238,9 +239,8 @@ public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) {
238239
*/
239240
public AbstractObjectAssert<?, Object> getBean(String name) {
240241
if (this.startupFailure != null) {
241-
throwAssertionError(new BasicErrorMessageFactory(
242-
"%nExpecting:%n <%s>%nto contain a bean of name:%n <%s>%nbut context failed to start",
243-
getApplicationContext(), name));
242+
throwAssertionError(contextFailedToStartWhenExpecting(
243+
"to contain a bean of name:%n <%s>", name));
244244
}
245245
Object bean = findBean(name);
246246
return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name,
@@ -267,9 +267,8 @@ public AbstractObjectAssert<?, Object> getBean(String name) {
267267
@SuppressWarnings("unchecked")
268268
public <T> AbstractObjectAssert<?, T> getBean(String name, Class<T> type) {
269269
if (this.startupFailure != null) {
270-
throwAssertionError(new BasicErrorMessageFactory(
271-
"%nExpecting:%n <%s>%nto contain a bean of name:%n <%s> (%s)%nbut context failed to start",
272-
getApplicationContext(), name, type));
270+
throwAssertionError(contextFailedToStartWhenExpecting(
271+
"to contain a bean of name:%n <%s> (%s)", name, type));
273272
}
274273
Object bean = findBean(name);
275274
if (bean != null && type != null && !type.isInstance(bean)) {
@@ -307,9 +306,8 @@ private Object findBean(String name) {
307306
*/
308307
public <T> MapAssert<String, T> getBeans(Class<T> type) {
309308
if (this.startupFailure != null) {
310-
throwAssertionError(new BasicErrorMessageFactory(
311-
"%nExpecting:%n <%s>%nto get beans of type:%n <%s> (%s)%nbut context failed to start",
312-
getApplicationContext(), type, type));
309+
throwAssertionError(contextFailedToStartWhenExpecting(
310+
"to get beans of type:%n <%s>", type));
313311
}
314312
return Assertions.assertThat(getApplicationContext().getBeansOfType(type))
315313
.as("Beans of type <%s> from <%s>", type, getApplicationContext());
@@ -357,9 +355,7 @@ public ApplicationContextAssert<C> hasFailed() {
357355
*/
358356
public ApplicationContextAssert<C> hasNotFailed() {
359357
if (this.startupFailure != null) {
360-
throwAssertionError(new BasicErrorMessageFactory(
361-
"%nExpecting:%n <%s>%nto have not failed:%nbut context failed to start",
362-
getApplicationContext()));
358+
throwAssertionError(contextFailedToStartWhenExpecting("to have not failed"));
363359
}
364360
return this;
365361
}
@@ -372,4 +368,61 @@ protected final Throwable getStartupFailure() {
372368
return this.startupFailure;
373369
}
374370

371+
private ContextFailedToStart<C> contextFailedToStartWhenExpecting(
372+
String expectationFormat, Object... arguments) {
373+
return new ContextFailedToStart<C>(getApplicationContext(), this.startupFailure,
374+
expectationFormat, arguments);
375+
}
376+
377+
private static final class ContextFailedToStart<C extends ApplicationContext>
378+
extends BasicErrorMessageFactory {
379+
380+
private ContextFailedToStart(C context, Throwable ex, String expectationFormat,
381+
Object... arguments) {
382+
super("%nExpecting:%n <%s>%n" + expectationFormat
383+
+ ":%nbut context failed to start:%n%s",
384+
combineArguments(context.toString(), ex, arguments));
385+
}
386+
387+
private static Object[] combineArguments(String context, Throwable ex,
388+
Object[] arguments) {
389+
Object[] combinedArguments = new Object[arguments.length + 2];
390+
combinedArguments[0] = unquotedString(context);
391+
System.arraycopy(arguments, 0, combinedArguments, 1, arguments.length);
392+
combinedArguments[combinedArguments.length - 1] = unquotedString(
393+
getIndentedStackTraceAsString(ex));
394+
return combinedArguments;
395+
}
396+
397+
private static String getIndentedStackTraceAsString(Throwable ex) {
398+
String stackTrace = getStackTraceAsString(ex);
399+
return indent(stackTrace);
400+
}
401+
402+
private static String getStackTraceAsString(Throwable ex) {
403+
StringWriter writer = new StringWriter();
404+
PrintWriter printer = new PrintWriter(writer);
405+
ex.printStackTrace(printer);
406+
return writer.toString();
407+
}
408+
409+
private static String indent(String input) {
410+
BufferedReader reader = new BufferedReader(new StringReader(input));
411+
StringWriter writer = new StringWriter();
412+
PrintWriter printer = new PrintWriter(writer);
413+
try {
414+
String line;
415+
while ((line = reader.readLine()) != null) {
416+
printer.print(" ");
417+
printer.println(line);
418+
}
419+
return writer.toString();
420+
}
421+
catch (IOException ex) {
422+
return input;
423+
}
424+
}
425+
426+
}
427+
375428
}

0 commit comments

Comments
 (0)