001/* 002 * Copyright 2012-2018 the original author or authors. 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 */ 016 017package org.springframework.boot.test.context.assertj; 018 019import java.io.BufferedReader; 020import java.io.PrintWriter; 021import java.io.StringReader; 022import java.io.StringWriter; 023import java.util.Map; 024 025import org.assertj.core.api.AbstractAssert; 026import org.assertj.core.api.AbstractObjectArrayAssert; 027import org.assertj.core.api.AbstractObjectAssert; 028import org.assertj.core.api.AbstractThrowableAssert; 029import org.assertj.core.api.Assertions; 030import org.assertj.core.api.MapAssert; 031import org.assertj.core.error.BasicErrorMessageFactory; 032 033import org.springframework.beans.factory.BeanFactoryUtils; 034import org.springframework.beans.factory.NoSuchBeanDefinitionException; 035import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 036import org.springframework.boot.test.context.runner.ApplicationContextRunner; 037import org.springframework.context.ApplicationContext; 038import org.springframework.context.ConfigurableApplicationContext; 039import org.springframework.util.Assert; 040 041import static org.assertj.core.api.Assertions.assertThat; 042 043/** 044 * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to an 045 * {@link ApplicationContext}. 046 * 047 * @param <C> the application context type 048 * @author Phillip Webb 049 * @author Andy Wilkinson 050 * @since 2.0.0 051 * @see ApplicationContextRunner 052 * @see AssertableApplicationContext 053 */ 054public class ApplicationContextAssert<C extends ApplicationContext> 055 extends AbstractAssert<ApplicationContextAssert<C>, C> { 056 057 private final Throwable startupFailure; 058 059 /** 060 * Create a new {@link ApplicationContextAssert} instance. 061 * @param applicationContext the source application context 062 * @param startupFailure the startup failure or {@code null} 063 */ 064 ApplicationContextAssert(C applicationContext, Throwable startupFailure) { 065 super(applicationContext, ApplicationContextAssert.class); 066 Assert.notNull(applicationContext, "ApplicationContext must not be null"); 067 this.startupFailure = startupFailure; 068 } 069 070 /** 071 * Verifies that the application context contains a bean with the given name. 072 * <p> 073 * Example: <pre class="code"> 074 * assertThat(context).hasBean("fooBean"); </pre> 075 * @param name the name of the bean 076 * @return {@code this} assertion object. 077 * @throws AssertionError if the application context did not start 078 * @throws AssertionError if the application context does not contain a bean with the 079 * given name 080 */ 081 public ApplicationContextAssert<C> hasBean(String name) { 082 if (this.startupFailure != null) { 083 throwAssertionError(contextFailedToStartWhenExpecting( 084 "to have bean named:%n <%s>", name)); 085 } 086 if (findBean(name) == null) { 087 throwAssertionError(new BasicErrorMessageFactory( 088 "%nExpecting:%n <%s>%nto have bean named:%n <%s>%nbut found no such bean", 089 getApplicationContext(), name)); 090 } 091 return this; 092 } 093 094 /** 095 * Verifies that the application context (or ancestors) contains a single bean with 096 * the given type. 097 * <p> 098 * Example: <pre class="code"> 099 * assertThat(context).hasSingleBean(Foo.class); </pre> 100 * @param type the bean type 101 * @return {@code this} assertion object. 102 * @throws AssertionError if the application context did not start 103 * @throws AssertionError if the application context does no beans of the given type 104 * @throws AssertionError if the application context contains multiple beans of the 105 * given type 106 */ 107 public ApplicationContextAssert<C> hasSingleBean(Class<?> type) { 108 return hasSingleBean(type, Scope.INCLUDE_ANCESTORS); 109 } 110 111 /** 112 * Verifies that the application context contains a single bean with the given type. 113 * <p> 114 * Example: <pre class="code"> 115 * assertThat(context).hasSingleBean(Foo.class, Scope.NO_ANCESTORS); </pre> 116 * @param type the bean type 117 * @param scope the scope of the assertion 118 * @return {@code this} assertion object. 119 * @throws AssertionError if the application context did not start 120 * @throws AssertionError if the application context does no beans of the given type 121 * @throws AssertionError if the application context contains multiple beans of the 122 * given type 123 */ 124 public ApplicationContextAssert<C> hasSingleBean(Class<?> type, Scope scope) { 125 Assert.notNull(scope, "Scope must not be null"); 126 if (this.startupFailure != null) { 127 throwAssertionError(contextFailedToStartWhenExpecting( 128 "to have a single bean of type:%n <%s>", type)); 129 } 130 String[] names = scope.getBeanNamesForType(getApplicationContext(), type); 131 if (names.length == 0) { 132 throwAssertionError(new BasicErrorMessageFactory( 133 "%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found no beans of that type", 134 getApplicationContext(), type)); 135 } 136 if (names.length > 1) { 137 throwAssertionError(new BasicErrorMessageFactory( 138 "%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found:%n <%s>", 139 getApplicationContext(), type, names)); 140 } 141 return this; 142 } 143 144 /** 145 * Verifies that the application context (or ancestors) does not contain any beans of 146 * the given type. 147 * <p> 148 * Example: <pre class="code"> 149 * assertThat(context).doesNotHaveBean(Foo.class); </pre> 150 * @param type the bean type 151 * @return {@code this} assertion object. 152 * @throws AssertionError if the application context did not start 153 * @throws AssertionError if the application context contains any beans of the given 154 * type 155 */ 156 public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type) { 157 return doesNotHaveBean(type, Scope.INCLUDE_ANCESTORS); 158 } 159 160 /** 161 * Verifies that the application context does not contain any beans of the given type. 162 * <p> 163 * Example: <pre class="code"> 164 * assertThat(context).doesNotHaveBean(Foo.class, Scope.NO_ANCESTORS); </pre> 165 * @param type the bean type 166 * @param scope the scope of the assertion 167 * @return {@code this} assertion object. 168 * @throws AssertionError if the application context did not start 169 * @throws AssertionError if the application context contains any beans of the given 170 * type 171 */ 172 public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type, Scope scope) { 173 Assert.notNull(scope, "Scope must not be null"); 174 if (this.startupFailure != null) { 175 throwAssertionError(contextFailedToStartWhenExpecting( 176 "not to have any beans of type:%n <%s>", type)); 177 } 178 String[] names = scope.getBeanNamesForType(getApplicationContext(), type); 179 if (names.length > 0) { 180 throwAssertionError(new BasicErrorMessageFactory( 181 "%nExpecting:%n <%s>%nnot to have a beans of type:%n <%s>%nbut found:%n <%s>", 182 getApplicationContext(), type, names)); 183 } 184 return this; 185 } 186 187 /** 188 * Verifies that the application context does not contain a beans of the given name. 189 * <p> 190 * Example: <pre class="code"> 191 * assertThat(context).doesNotHaveBean("fooBean"); </pre> 192 * @param name the name of the bean 193 * @return {@code this} assertion object. 194 * @throws AssertionError if the application context did not start 195 * @throws AssertionError if the application context contains a beans of the given 196 * name 197 */ 198 public ApplicationContextAssert<C> doesNotHaveBean(String name) { 199 if (this.startupFailure != null) { 200 throwAssertionError(contextFailedToStartWhenExpecting( 201 "not to have any beans of name:%n <%s>", name)); 202 } 203 try { 204 Object bean = getApplicationContext().getBean(name); 205 throwAssertionError(new BasicErrorMessageFactory( 206 "%nExpecting:%n <%s>%nnot to have a bean of name:%n <%s>%nbut found:%n <%s>", 207 getApplicationContext(), name, bean)); 208 } 209 catch (NoSuchBeanDefinitionException ex) { 210 } 211 return this; 212 } 213 214 /** 215 * Obtain the beans names of the given type from the application context, the names 216 * becoming the object array under test. 217 * <p> 218 * Example: <pre class="code"> 219 * assertThat(context).getBeanNames(Foo.class).containsOnly("fooBean"); </pre> 220 * @param <T> the bean type 221 * @param type the bean type 222 * @return array assertions for the bean names 223 * @throws AssertionError if the application context did not start 224 */ 225 public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) { 226 if (this.startupFailure != null) { 227 throwAssertionError(contextFailedToStartWhenExpecting( 228 "to get beans names with type:%n <%s>", type)); 229 } 230 return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type)) 231 .as("Bean names of type <%s> from <%s>", type, getApplicationContext()); 232 } 233 234 /** 235 * Obtain a single bean of the given type from the application context (or ancestors), 236 * the bean becoming the object under test. If no beans of the specified type can be 237 * found an assert on {@code null} is returned. 238 * <p> 239 * Example: <pre class="code"> 240 * assertThat(context).getBean(Foo.class).isInstanceOf(DefaultFoo.class); 241 * assertThat(context).getBean(Bar.class).isNull();</pre> 242 * @param <T> the bean type 243 * @param type the bean type 244 * @return bean assertions for the bean, or an assert on {@code null} if the no bean 245 * is found 246 * @throws AssertionError if the application context did not start 247 * @throws AssertionError if the application context contains multiple beans of the 248 * given type 249 */ 250 public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) { 251 return getBean(type, Scope.INCLUDE_ANCESTORS); 252 } 253 254 /** 255 * Obtain a single bean of the given type from the application context, the bean 256 * becoming the object under test. If no beans of the specified type can be found an 257 * assert on {@code null} is returned. 258 * <p> 259 * Example: <pre class="code"> 260 * assertThat(context).getBean(Foo.class, Scope.NO_ANCESTORS).isInstanceOf(DefaultFoo.class); 261 * assertThat(context).getBean(Bar.class, Scope.NO_ANCESTORS).isNull();</pre> 262 * @param <T> the bean type 263 * @param type the bean type 264 * @param scope the scope of the assertion 265 * @return bean assertions for the bean, or an assert on {@code null} if the no bean 266 * is found 267 * @throws AssertionError if the application context did not start 268 * @throws AssertionError if the application context contains multiple beans of the 269 * given type 270 */ 271 public <T> AbstractObjectAssert<?, T> getBean(Class<T> type, Scope scope) { 272 Assert.notNull(scope, "Scope must not be null"); 273 if (this.startupFailure != null) { 274 throwAssertionError(contextFailedToStartWhenExpecting( 275 "to contain bean of type:%n <%s>", type)); 276 } 277 String[] names = scope.getBeanNamesForType(getApplicationContext(), type); 278 String name = (names.length > 0) ? getPrimary(names, scope) : null; 279 if (names.length > 1 && name == null) { 280 throwAssertionError(new BasicErrorMessageFactory( 281 "%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>", 282 getApplicationContext(), type, names)); 283 } 284 T bean = (name != null) ? getApplicationContext().getBean(name, type) : null; 285 return Assertions.assertThat(bean).as("Bean of type <%s> from <%s>", type, 286 getApplicationContext()); 287 } 288 289 private String getPrimary(String[] names, Scope scope) { 290 if (names.length == 1) { 291 return names[0]; 292 } 293 String primary = null; 294 for (String name : names) { 295 if (isPrimary(name, scope)) { 296 if (primary != null) { 297 return null; 298 } 299 primary = name; 300 } 301 } 302 return primary; 303 } 304 305 private boolean isPrimary(String name, Scope scope) { 306 ApplicationContext context = getApplicationContext(); 307 while (context != null) { 308 if (context instanceof ConfigurableApplicationContext) { 309 ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) context) 310 .getBeanFactory(); 311 if (factory.containsBean(name) 312 && factory.getMergedBeanDefinition(name).isPrimary()) { 313 return true; 314 } 315 } 316 context = (scope != Scope.NO_ANCESTORS) ? context.getParent() : null; 317 } 318 return false; 319 } 320 321 /** 322 * Obtain a single bean of the given name from the application context, the bean 323 * becoming the object under test. If no bean of the specified name can be found an 324 * assert on {@code null} is returned. 325 * <p> 326 * Example: <pre class="code"> 327 * assertThat(context).getBean("foo").isInstanceOf(Foo.class); 328 * assertThat(context).getBean("foo").isNull();</pre> 329 * @param name the name of the bean 330 * @return bean assertions for the bean, or an assert on {@code null} if the no bean 331 * is found 332 * @throws AssertionError if the application context did not start 333 */ 334 public AbstractObjectAssert<?, Object> getBean(String name) { 335 if (this.startupFailure != null) { 336 throwAssertionError(contextFailedToStartWhenExpecting( 337 "to contain a bean of name:%n <%s>", name)); 338 } 339 Object bean = findBean(name); 340 return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name, 341 getApplicationContext()); 342 } 343 344 /** 345 * Obtain a single bean of the given name and type from the application context, the 346 * bean becoming the object under test. If no bean of the specified name can be found 347 * an assert on {@code null} is returned. 348 * <p> 349 * Example: <pre class="code"> 350 * assertThat(context).getBean("foo", Foo.class).isInstanceOf(DefaultFoo.class); 351 * assertThat(context).getBean("foo", Foo.class).isNull();</pre> 352 * @param <T> the bean type 353 * @param name the name of the bean 354 * @param type the bean type 355 * @return bean assertions for the bean, or an assert on {@code null} if the no bean 356 * is found 357 * @throws AssertionError if the application context did not start 358 * @throws AssertionError if the application context contains a bean with the given 359 * name but a different type 360 */ 361 @SuppressWarnings("unchecked") 362 public <T> AbstractObjectAssert<?, T> getBean(String name, Class<T> type) { 363 if (this.startupFailure != null) { 364 throwAssertionError(contextFailedToStartWhenExpecting( 365 "to contain a bean of name:%n <%s> (%s)", name, type)); 366 } 367 Object bean = findBean(name); 368 if (bean != null && type != null && !type.isInstance(bean)) { 369 throwAssertionError(new BasicErrorMessageFactory( 370 "%nExpecting:%n <%s>%nto contain a bean of name:%n <%s> (%s)%nbut found:%n <%s> of type <%s>", 371 getApplicationContext(), name, type, bean, bean.getClass())); 372 } 373 return Assertions.assertThat((T) bean).as( 374 "Bean of name <%s> and type <%s> from <%s>", name, type, 375 getApplicationContext()); 376 } 377 378 private Object findBean(String name) { 379 try { 380 return getApplicationContext().getBean(name); 381 } 382 catch (NoSuchBeanDefinitionException ex) { 383 return null; 384 } 385 } 386 387 /** 388 * Obtain a map bean names and instances of the given type from the application 389 * context (or ancestors), the map becoming the object under test. If no bean of the 390 * specified type can be found an assert on an empty {@code map} is returned. 391 * <p> 392 * Example: <pre class="code"> 393 * assertThat(context).getBeans(Foo.class).containsKey("foo"); 394 * </pre> 395 * @param <T> the bean type 396 * @param type the bean type 397 * @return bean assertions for the beans, or an assert on an empty {@code map} if the 398 * no beans are found 399 * @throws AssertionError if the application context did not start 400 */ 401 public <T> MapAssert<String, T> getBeans(Class<T> type) { 402 return getBeans(type, Scope.INCLUDE_ANCESTORS); 403 } 404 405 /** 406 * Obtain a map bean names and instances of the given type from the application 407 * context, the map becoming the object under test. If no bean of the specified type 408 * can be found an assert on an empty {@code map} is returned. 409 * <p> 410 * Example: <pre class="code"> 411 * assertThat(context).getBeans(Foo.class, Scope.NO_ANCESTORS).containsKey("foo"); 412 * </pre> 413 * @param <T> the bean type 414 * @param type the bean type 415 * @param scope the scope of the assertion 416 * @return bean assertions for the beans, or an assert on an empty {@code map} if the 417 * no beans are found 418 * @throws AssertionError if the application context did not start 419 */ 420 public <T> MapAssert<String, T> getBeans(Class<T> type, Scope scope) { 421 Assert.notNull(scope, "Scope must not be null"); 422 if (this.startupFailure != null) { 423 throwAssertionError(contextFailedToStartWhenExpecting( 424 "to get beans of type:%n <%s>", type)); 425 } 426 return Assertions.assertThat(scope.getBeansOfType(getApplicationContext(), type)) 427 .as("Beans of type <%s> from <%s>", type, getApplicationContext()); 428 } 429 430 /** 431 * Obtain the failure that stopped the application context from running, the failure 432 * becoming the object under test. 433 * <p> 434 * Example: <pre class="code"> 435 * assertThat(context).getFailure().containsMessage("missing bean"); 436 * </pre> 437 * @return assertions on the cause of the failure 438 * @throws AssertionError if the application context started without a failure 439 */ 440 public AbstractThrowableAssert<?, ? extends Throwable> getFailure() { 441 hasFailed(); 442 return assertThat(this.startupFailure); 443 } 444 445 /** 446 * Verifies that the application has failed to start. 447 * <p> 448 * Example: <pre class="code"> assertThat(context).hasFailed(); 449 * </pre> 450 * @return {@code this} assertion object. 451 * @throws AssertionError if the application context started without a failure 452 */ 453 public ApplicationContextAssert<C> hasFailed() { 454 if (this.startupFailure == null) { 455 throwAssertionError(new BasicErrorMessageFactory( 456 "%nExpecting:%n <%s>%nto have failed%nbut context started successfully", 457 getApplicationContext())); 458 } 459 return this; 460 } 461 462 /** 463 * Verifies that the application has not failed to start. 464 * <p> 465 * Example: <pre class="code"> assertThat(context).hasNotFailed(); 466 * </pre> 467 * @return {@code this} assertion object. 468 * @throws AssertionError if the application context failed to start 469 */ 470 public ApplicationContextAssert<C> hasNotFailed() { 471 if (this.startupFailure != null) { 472 throwAssertionError(contextFailedToStartWhenExpecting("to have not failed")); 473 } 474 return this; 475 } 476 477 protected final C getApplicationContext() { 478 return this.actual; 479 } 480 481 protected final Throwable getStartupFailure() { 482 return this.startupFailure; 483 } 484 485 private ContextFailedToStart<C> contextFailedToStartWhenExpecting( 486 String expectationFormat, Object... arguments) { 487 return new ContextFailedToStart<>(getApplicationContext(), this.startupFailure, 488 expectationFormat, arguments); 489 } 490 491 /** 492 * The scope of an assertion. 493 */ 494 public enum Scope { 495 496 /** 497 * Limited to the current context. 498 */ 499 NO_ANCESTORS { 500 501 @Override 502 String[] getBeanNamesForType(ApplicationContext applicationContext, 503 Class<?> type) { 504 return applicationContext.getBeanNamesForType(type); 505 } 506 507 @Override 508 <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, 509 Class<T> type) { 510 return applicationContext.getBeansOfType(type); 511 } 512 513 }, 514 515 /** 516 * Consider the ancestor contexts as well as the current context. 517 */ 518 INCLUDE_ANCESTORS { 519 520 @Override 521 String[] getBeanNamesForType(ApplicationContext applicationContext, 522 Class<?> type) { 523 return BeanFactoryUtils 524 .beanNamesForTypeIncludingAncestors(applicationContext, type); 525 } 526 527 @Override 528 <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, 529 Class<T> type) { 530 return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 531 type); 532 } 533 534 }; 535 536 abstract String[] getBeanNamesForType(ApplicationContext applicationContext, 537 Class<?> type); 538 539 abstract <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, 540 Class<T> type); 541 542 } 543 544 private static final class ContextFailedToStart<C extends ApplicationContext> 545 extends BasicErrorMessageFactory { 546 547 private ContextFailedToStart(C context, Throwable ex, String expectationFormat, 548 Object... arguments) { 549 super("%nExpecting:%n <%s>%n" + expectationFormat 550 + ":%nbut context failed to start:%n%s", 551 combineArguments(context.toString(), ex, arguments)); 552 } 553 554 private static Object[] combineArguments(String context, Throwable ex, 555 Object[] arguments) { 556 Object[] combinedArguments = new Object[arguments.length + 2]; 557 combinedArguments[0] = unquotedString(context); 558 System.arraycopy(arguments, 0, combinedArguments, 1, arguments.length); 559 combinedArguments[combinedArguments.length - 1] = unquotedString( 560 getIndentedStackTraceAsString(ex)); 561 return combinedArguments; 562 } 563 564 private static String getIndentedStackTraceAsString(Throwable ex) { 565 String stackTrace = getStackTraceAsString(ex); 566 return indent(stackTrace); 567 } 568 569 private static String getStackTraceAsString(Throwable ex) { 570 StringWriter writer = new StringWriter(); 571 PrintWriter printer = new PrintWriter(writer); 572 ex.printStackTrace(printer); 573 return writer.toString(); 574 } 575 576 private static String indent(String input) { 577 BufferedReader reader = new BufferedReader(new StringReader(input)); 578 StringWriter writer = new StringWriter(); 579 PrintWriter printer = new PrintWriter(writer); 580 reader.lines().forEach((line) -> { 581 printer.print(" "); 582 printer.println(line); 583 }); 584 return writer.toString(); 585 } 586 587 } 588 589}