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}