diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestComponent.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestComponent.java
new file mode 100644
index 0000000000..0e5c25be62
--- /dev/null
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestComponent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.boot.context.TypeExcludeFilter;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.stereotype.Component;
+
+/**
+ * {@link Component @Component} that can be used when a bean is intended only for tests,
+ * and should be excluded from Spring Boot's component scanning.
+ *
+ * Note that if you directly use {@link ComponentScan @ComponentScan} rather than relying
+ * on {@code @SpringBootApplication} you should ensure that a {@link TypeExcludeFilter} is
+ * declared as an {@link ComponentScan#excludeFilters() excludeFilter}.
+ *
+ * @author Phillip Webb
+ * @since 1.4.0
+ * @see TypeExcludeFilter
+ * @see TestConfiguration
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Component
+public @interface TestComponent {
+
+ /**
+ * The value may indicate a suggestion for a logical component name, to be turned into
+ * a Spring bean in case of an auto-detected component.
+ */
+ String value() default "";
+
+}
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizer.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizer.java
new file mode 100644
index 0000000000..678bd3ef49
--- /dev/null
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.MergedContextConfiguration;
+
+/**
+ * {@link ContextCustomizer} to add the {@link TestTypeExcludeFilter} to the
+ * {@link ApplicationContext}.
+ *
+ * @author Phillip Webb
+ */
+class ExcludeFilterContextCustomizer implements ContextCustomizer {
+
+ @Override
+ public void customizeContext(ConfigurableApplicationContext context,
+ MergedContextConfiguration mergedContextConfiguration) {
+ context.getBeanFactory().registerSingleton(TestTypeExcludeFilter.class.getName(),
+ new TestTypeExcludeFilter());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizerFactory.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizerFactory.java
new file mode 100644
index 0000000000..72ad2047b8
--- /dev/null
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/ExcludeFilterContextCustomizerFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import java.util.List;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+
+/**
+ * {@link ContextCustomizerFactory} to add the {@link TestTypeExcludeFilter} to the
+ * {@link ApplicationContext}.
+ *
+ * @author Phillip Webb
+ * @see ExcludeFilterContextCustomizer
+ */
+class ExcludeFilterContextCustomizerFactory implements ContextCustomizerFactory {
+
+ @Override
+ public ContextCustomizer createContextCustomizer(Class> testClass,
+ List configAttributes) {
+ return new ExcludeFilterContextCustomizer();
+ }
+
+}
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java
new file mode 100644
index 0000000000..2a10438e37
--- /dev/null
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import java.io.IOException;
+
+import org.springframework.boot.context.TypeExcludeFilter;
+import org.springframework.boot.test.context.TestComponent;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+
+/**
+ * {@link TypeExcludeFilter} to exclude classes annotated with {@link TestComponent} as
+ * well as inner-classes of tests.
+ *
+ * @author Phillip Webb
+ */
+class TestTypeExcludeFilter extends TypeExcludeFilter {
+
+ private static final String TEST_ANNOTATION = "org.junit.Test";
+
+ @Override
+ public boolean match(MetadataReader metadataReader,
+ MetadataReaderFactory metadataReaderFactory) throws IOException {
+ if (isTestConfiguration(metadataReader)) {
+ return true;
+ }
+ if (isTestClass(metadataReader)) {
+ return true;
+ }
+ String enclosing = metadataReader.getClassMetadata().getEnclosingClassName();
+ if (enclosing != null) {
+ try {
+ if (match(metadataReaderFactory.getMetadataReader(enclosing),
+ metadataReaderFactory)) {
+ return true;
+ }
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
+ }
+ return false;
+ }
+
+ private boolean isTestConfiguration(MetadataReader metadataReader) {
+ return (metadataReader.getAnnotationMetadata()
+ .isAnnotated(TestComponent.class.getName()));
+ }
+
+ private boolean isTestClass(MetadataReader metadataReader) {
+ return !metadataReader.getAnnotationMetadata()
+ .getAnnotatedMethods(TEST_ANNOTATION).isEmpty();
+ }
+
+}
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java
new file mode 100644
index 0000000000..80bbd7fc13
--- /dev/null
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Test support for {@link org.springframework.boot.context.TypeExcludeFilter}.
+ */
+package org.springframework.boot.test.context.filter;
diff --git a/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-test/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..08e4fa5ad5
--- /dev/null
+++ b/spring-boot-test/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+# Spring Test ContextCustomizerFactories
+org.springframework.test.context.ContextCustomizerFactory=\
+org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory
diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleConfig.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleConfig.java
new file mode 100644
index 0000000000..d56e2849d3
--- /dev/null
+++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleConfig.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SampleConfig {
+
+}
diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleTestConfig.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleTestConfig.java
new file mode 100644
index 0000000000..0be1f7bf60
--- /dev/null
+++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/SampleTestConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import org.springframework.boot.test.context.TestComponent;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@TestComponent
+public class SampleTestConfig {
+
+}
diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilterTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilterTests.java
new file mode 100644
index 0000000000..ee1b685576
--- /dev/null
+++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilterTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.context.filter;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link TestTypeExcludeFilter}.
+ *
+ * @author Phillip Webb
+ */
+public class TestTypeExcludeFilterTests {
+
+ private TestTypeExcludeFilter filter = new TestTypeExcludeFilter();
+
+ private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
+
+ @Test
+ public void matchesTestClass() throws Exception {
+ assertThat(this.filter.match(getMetadataReader(TestTypeExcludeFilterTests.class),
+ this.metadataReaderFactory)).isTrue();
+ }
+
+ @Test
+ public void matchesNestedConfiguration() throws Exception {
+ assertThat(this.filter.match(getMetadataReader(NestedConfig.class),
+ this.metadataReaderFactory)).isTrue();
+ }
+
+ @Test
+ public void matchesTestConfiguration() throws Exception {
+ assertThat(this.filter.match(getMetadataReader(SampleTestConfig.class),
+ this.metadataReaderFactory)).isTrue();
+ }
+
+ @Test
+ public void doesNotMatchRegularConfiguration() throws Exception {
+ assertThat(this.filter.match(getMetadataReader(SampleConfig.class),
+ this.metadataReaderFactory)).isFalse();
+ }
+
+ private MetadataReader getMetadataReader(Class> source) throws IOException {
+ return this.metadataReaderFactory.getMetadataReader(source.getName());
+ }
+
+ @Configuration
+ static class NestedConfig {
+
+ }
+
+}