From 81d56355715a58b511257eb79a3059477652138e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 22 Mar 2016 11:42:47 -0700 Subject: [PATCH] Add @DataJpaTest annotation Add @DataJpaTest and @AutoConfigureTestDatabase annotations to allow testing of an application "slice" that only deals JPA. See gh-4901 --- spring-boot-test-autoconfigure/pom.xml | 42 +++ .../orm/jpa/AutoConfigureTestDatabase.java | 78 ++++++ .../jpa/AutoConfigureTestEntityManager.java | 40 +++ .../autoconfigure/orm/jpa/DataJpaTest.java | 97 +++++++ .../orm/jpa/DataJpaTypeExcludeFilter.java | 76 ++++++ .../jpa/ImportDataJpaAutoConfiguration.java | 50 ++++ .../jpa/TestDatabaseAutoConfiguration.java | 192 ++++++++++++++ .../orm/jpa/TestEntityManager.java | 245 ++++++++++++++++++ .../TestEntityManagerAutoConfiguration.java | 46 ++++ .../autoconfigure/orm/jpa/package-info.java | 20 ++ .../orm/jpa/DataJpaTestIntegrationTests.java | 95 +++++++ ...ReplaceAutoConfiguredIntegrationTests.java | 74 ++++++ ...iguredWithoutOverrideIntegrationTests.java | 66 +++++ ...tabaseReplaceExplicitIntegrationTests.java | 84 ++++++ ...stDatabaseReplaceNoneIntegrationTests.java | 66 +++++ .../orm/jpa/ExampleComponent.java | 29 +++ .../orm/jpa/ExampleDataJpaApplication.java | 41 +++ .../autoconfigure/orm/jpa/ExampleEntity.java | 56 ++++ .../orm/jpa/ExampleRepository.java | 30 +++ .../orm/jpa/TestEntityManagerTests.java | 241 +++++++++++++++++ 20 files changed, 1668 insertions(+) create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestDatabase.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestEntityManager.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTypeExcludeFilter.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/ImportDataJpaAutoConfiguration.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestDatabaseAutoConfiguration.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerAutoConfiguration.java create mode 100644 spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/package-info.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleComponent.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleDataJpaApplication.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleEntity.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleRepository.java create mode 100644 spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerTests.java diff --git a/spring-boot-test-autoconfigure/pom.xml b/spring-boot-test-autoconfigure/pom.xml index 5292eec371..a0efa01f17 100644 --- a/spring-boot-test-autoconfigure/pom.xml +++ b/spring-boot-test-autoconfigure/pom.xml @@ -34,6 +34,11 @@ javax.servlet-api true + + javax.transaction + javax.transaction-api + true + com.fasterxml.jackson.core jackson-databind @@ -45,12 +50,28 @@ 2.19 true + + org.hibernate + hibernate-entitymanager + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + true + org.seleniumhq.selenium selenium-htmlunit-driver 2.52.0 true + + org.springframework + spring-orm + true + org.springframework spring-test @@ -66,6 +87,17 @@ spring-webmvc true + + org.springframework.data + spring-data-jpa + + + org.aspectj + aspectjrt + + + true + org.aspectj @@ -77,6 +109,16 @@ aspectjweaver test + + com.h2database + h2 + test + + + org.hsqldb + hsqldb + test + org.skyscreamer jsonassert diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestDatabase.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestDatabase.java new file mode 100644 index 0000000000..51ab895eee --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestDatabase.java @@ -0,0 +1,78 @@ +/* + * 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.autoconfigure.orm.jpa; + +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 javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; + +/** + * Annotation that can be applied to a test class to configure a test database to use + * instead of any application defined or auto-configured {@link DataSource}. + * + * @author Phillip Webb + * @see TestDatabaseAutoConfiguration + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@ImportAutoConfiguration(TestDatabaseAutoConfiguration.class) +@PropertyMapping("spring.test.database") +public @interface AutoConfigureTestDatabase { + + /** + * If the test database should replace any existing. + */ + Replace replace() default Replace.ANY; + + /** + * The type of connection to be establish when {@link #replace() replacing} the data + * source. By default will attempt to detect the connection based on the classpath. + */ + EmbeddedDatabaseConnection connection() default EmbeddedDatabaseConnection.NONE; + + /** + * What the test database can replace. + */ + enum Replace { + + /** + * Replace any DataSource bean (auto-configured or manually defined). + */ + ANY, + + /** + * Only replace auto-configured DataSource. + */ + AUTO_CONFIGURED, + + /** + * Don't replace the application default DataSource. + */ + NONE + + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestEntityManager.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestEntityManager.java new file mode 100644 index 0000000000..247567751c --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/AutoConfigureTestEntityManager.java @@ -0,0 +1,40 @@ +/* + * 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.autoconfigure.orm.jpa; + +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.autoconfigure.ImportAutoConfiguration; + +/** + * Annotation that can be applied to a test class to enable and configure + * auto-configuration of a {@link TestEntityManager}. + * + * @author Phillip Webb + * @see TestEntityManagerAutoConfiguration + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@ImportAutoConfiguration(TestEntityManagerAutoConfiguration.class) +public @interface AutoConfigureTestEntityManager { + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.java new file mode 100644 index 0000000000..3fa3466656 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.java @@ -0,0 +1,97 @@ +/* + * 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.autoconfigure.orm.jpa; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.boot.test.context.SpringApplicationTest; +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.test.context.BootstrapWith; +import org.springframework.transaction.annotation.Transactional; + +/** + * Annotation that can be used in combination with {@code @RunWith(SpringRunner.class)} + * for a typical JPA test. Can be used when a test focuses only on JPA + * components. + *

+ * Using this annotation will disable full auto-configuration and instead apply only + * configuration relevant to JPA tests. + *

+ * By default, tests annotated with {@code @DataJpaTest} will use an embedded in-memory + * database (replacing any explicit or usually auto-configured DataSource). The + * {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase} annotation can be used to + * override these settings. + *

+ * If you are looking to load your full application configuration, but use an embedded + * database, you should consider {@link SpringApplicationTest @SpringApplicationTest} + * combined with {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase} rather than + * this annotation. + * + * @author Phillip Webb + * @see AutoConfigureTestDatabase + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@BootstrapWith(SpringBootTestContextBootstrapper.class) +@OverrideAutoConfiguration(enabled = false) +@TypeExcludeFilters(DataJpaTypeExcludeFilter.class) +@Transactional +@ImportDataJpaAutoConfiguration +@AutoConfigureTestDatabase +@AutoConfigureTestEntityManager +public @interface DataJpaTest { + + /** + * If SQL output should be logged. + */ + @PropertyMapping("spring.jpa.show-sql") + boolean showSql() default true; + + /** + * Determines if default filtering should be used with + * {@link SpringBootApplication @SpringBootApplication}. By default no beans are + * included. + * @see #includeFilters() + * @see #excludeFilters() + */ + boolean useDefaultFilters() default true; + + /** + * A set of include filters which can be used to add otherwise filtered beans to the + * application context. + */ + Filter[] includeFilters() default {}; + + /** + * A set of exclude filters which can be used to filter beans that would otherwise be + * added to the application context. + */ + Filter[] excludeFilters() default {}; + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTypeExcludeFilter.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTypeExcludeFilter.java new file mode 100644 index 0000000000..cc8063654b --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTypeExcludeFilter.java @@ -0,0 +1,76 @@ +/* + * 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.autoconfigure.orm.jpa; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.boot.test.autoconfigure.filter.AnnotationCustomizableTypeExcludeFilter; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +/** + * {@link TypeExcludeFilter} for {@link DataJpaTest @DataJpaTest}. + * + * @author Phillip Webb + */ +class DataJpaTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter { + + private final DataJpaTest annotation; + + DataJpaTypeExcludeFilter(Class testClass) { + this.annotation = AnnotatedElementUtils.getMergedAnnotation(testClass, + DataJpaTest.class); + } + + @Override + protected boolean hasAnnotation() { + return this.annotation != null; + } + + @Override + protected Filter[] getFilters(FilterType type) { + switch (type) { + case INCLUDE: + return this.annotation.includeFilters(); + case EXCLUDE: + return this.annotation.excludeFilters(); + } + throw new IllegalStateException("Unsupported type " + type); + } + + @Override + protected boolean isUseDefaultFilters() { + return this.annotation.useDefaultFilters(); + } + + @Override + protected boolean defaultInclude(MetadataReader metadataReader, + MetadataReaderFactory metadataReaderFactory) throws IOException { + return false; + } + + @Override + protected Set> getDefaultIncudes() { + return Collections.emptySet(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/ImportDataJpaAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/ImportDataJpaAutoConfiguration.java new file mode 100644 index 0000000000..05de6d222a --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/ImportDataJpaAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * 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.autoconfigure.orm.jpa; + +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.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; + +/** + * {@link ImportAutoConfiguration Auto-configuration imports} for typical Data JPA tests. + * Most tests should consider using {@link DataJpaTest @DataJpaTest} rather than using + * this annotation directly. + * + * @author Phillip Webb + * @since 1.4.0 + * @see DataJpaTest + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ImportAutoConfiguration({ HibernateJpaAutoConfiguration.class, + JpaRepositoriesAutoConfiguration.class, TransactionAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + DataSourceAutoConfiguration.class }) +public @interface ImportDataJpaAutoConfiguration { + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestDatabaseAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestDatabaseAutoConfiguration.java new file mode 100644 index 0000000000..2ff86acdeb --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestDatabaseAutoConfiguration.java @@ -0,0 +1,192 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Auto-configuration for a test database. + * + * @author Phillip Webb + * @see AutoConfigureTestDatabase + */ +@Configuration +@AutoConfigureBefore(DataSourceAutoConfiguration.class) +class TestDatabaseAutoConfiguration { + + private final Environment environment; + + TestDatabaseAutoConfiguration(Environment environment) { + this.environment = environment; + } + + @Bean + @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "AUTO_CONFIGURED") + @ConditionalOnMissingBean + public DataSource dataSource() { + return new EmbeddedDataSourceFactory(this.environment).getEmbeddedDatabase(); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "ANY", matchIfMissing = true) + public static EmbeddedDataSourceBeanFactoryPostProcessor embeddedDataSourceBeanFactoryPostProcessor() { + return new EmbeddedDataSourceBeanFactoryPostProcessor(); + } + + @Order(Ordered.LOWEST_PRECEDENCE) + private static class EmbeddedDataSourceBeanFactoryPostProcessor + implements BeanDefinitionRegistryPostProcessor { + + private static final Log logger = LogFactory + .getLog(EmbeddedDataSourceBeanFactoryPostProcessor.class); + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) + throws BeansException { + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry, + "Test Database Auto-configuration can only be " + + "used with a ConfigurableListableBeanFactory"); + process(registry, (ConfigurableListableBeanFactory) registry); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + } + + private void process(BeanDefinitionRegistry registry, + ConfigurableListableBeanFactory beanFactory) { + BeanDefinitionHolder holder = getDataSourceBeanDefinition(beanFactory); + logger.info("Replacing '" + holder.getBeanName() + + "' DataSource bean with embedded version"); + registry.registerBeanDefinition(holder.getBeanName(), + createEmbeddedBeanDefinition()); + } + + private BeanDefinition createEmbeddedBeanDefinition() { + return new RootBeanDefinition(EmbeddedDataSourceFactoryBean.class); + } + + private BeanDefinitionHolder getDataSourceBeanDefinition( + ConfigurableListableBeanFactory beanFactory) { + String[] beanNames = beanFactory.getBeanNamesForType(DataSource.class); + if (!ObjectUtils.isEmpty(beanNames)) { + if (beanNames.length == 1) { + String beanName = beanNames[0]; + BeanDefinition beanDefinition = beanFactory + .getBeanDefinition(beanName); + return new BeanDefinitionHolder(beanDefinition, beanName); + } + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanFactory + .getBeanDefinition(beanName); + if (beanDefinition.isPrimary()) { + return new BeanDefinitionHolder(beanDefinition, beanName); + } + logger.warn("No primary DataSource found, " + + "embedded version will not be used"); + } + + } + return null; + } + + } + + private static class EmbeddedDataSourceFactoryBean + implements FactoryBean, EnvironmentAware, InitializingBean { + + private EmbeddedDataSourceFactory factory; + + private EmbeddedDatabase embeddedDatabase; + + @Override + public void setEnvironment(Environment environment) { + this.factory = new EmbeddedDataSourceFactory(environment); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.embeddedDatabase = this.factory.getEmbeddedDatabase(); + } + + @Override + public DataSource getObject() throws Exception { + return this.embeddedDatabase; + } + + @Override + public Class getObjectType() { + return EmbeddedDatabase.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + } + + private static class EmbeddedDataSourceFactory { + + private final Environment environment; + + EmbeddedDataSourceFactory(Environment environment) { + this.environment = environment; + } + + public EmbeddedDatabase getEmbeddedDatabase() { + EmbeddedDatabaseConnection connection = this.environment.getProperty( + "spring.test.database.connection", EmbeddedDatabaseConnection.class, + EmbeddedDatabaseConnection.NONE); + if (EmbeddedDatabaseConnection.NONE.equals(connection)) { + connection = EmbeddedDatabaseConnection.get(getClass().getClassLoader()); + } + return new EmbeddedDatabaseBuilder().setType(connection.getType()).build(); + } + + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.java new file mode 100644 index 0000000000..5bb62ab7a1 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.java @@ -0,0 +1,245 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceUnitUtil; + +import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.util.Assert; + +/** + * Alternative to {@link EntityManager} for use in JPA tests. Provides a subset of + * {@link EntityManager} methods that are useful for tests as well as helper methods for + * common testing tasks such as {@link #persistFlushFind(Object) persist/flush/find}. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class TestEntityManager { + + private final EntityManagerFactory entityManagerFactory; + + /** + * Create a new {@link TestEntityManager} instance for the given + * {@link EntityManagerFactory}. + * @param entityManagerFactory the source entity manager factory + */ + public TestEntityManager(EntityManagerFactory entityManagerFactory) { + Assert.notNull(entityManagerFactory, "EntityManagerFactory must not be null"); + this.entityManagerFactory = entityManagerFactory; + } + + /** + * Make an instance managed and persistent then return it's ID. Delegates to + * {@link EntityManager#persist(Object)} then {@link #getId(Object)}. + *

+ * Helpful when setting up test data in a test:

+	 * Object entityId = this.testEntityManager.persist(new MyEntity("Spring"));
+	 * 
+ * @param entity the source entity + * @return the ID of the newly persisted entity + */ + public Object persistAndGetId(Object entity) { + persist(entity); + return getId(entity); + } + + /** + * Make an instance managed and persistent then return it's ID. Delegates to + * {@link EntityManager#persist(Object)} then {@link #getId(Object, Class)}. + *

+ * Helpful when setting up test data in a test:

+	 * Long entityId = this.testEntityManager.persist(new MyEntity("Spring"), Long.class);
+	 * 
+ * @param the ID type + * @param entity the source entity + * @param idType the ID type + * @return the ID of the newly persisted entity + */ + public T persistAndGetId(Object entity, Class idType) { + persist(entity); + return getId(entity, idType); + + } + + /** + * Make an instance managed and persistent. Delegates to + * {@link EntityManager#persist(Object)} then returns the original source entity. + *

+ * Helpful when setting up test data in a test:

+	 * MyEntity entity = this.testEntityManager.persist(new MyEntity("Spring"));
+	 * 
+ * @param the entity type + * @param entity the entity to persist + * @return the persisted entity + */ + public E persist(E entity) { + Assert.state(getId(entity) == null, + "Entity " + entity.getClass().getName() + " already has an ID"); + getEntityManager().persist(entity); + return entity; + } + + /** + * Make an instance managed and persistent, synchronize the persistence context to the + * underlying database and finally find the persisted entity by its ID. Delegates to + * {@link #persistAndFlush(Object)} then {@link #find(Class, Object)} with the + * {@link #getId(Object) entity ID}. + *

+ * Helpful when ensuring that entity data is actually written and read from the + * underling database correctly. + * @param the entity type + * @param entity the entity to persist + * @return the entity found using the ID of the persisted entity + */ + @SuppressWarnings("unchecked") + public E persistFlushFind(E entity) { + EntityManager entityManager = getEntityManager(); + persistAndFlush(entity); + Object id = getId(entity); + entityManager.detach(entity); + return (E) entityManager.find(entity.getClass(), id); + } + + /** + * Make an instance managed and persistent then synchronize the persistence context to + * the underlying database. Delegates to {@link EntityManager#persist(Object)} then + * {@link #flush()} and finally returns the original source entity. + *

+ * Helpful when setting up test data in a test:

+	 * MyEntity entity = this.testEntityManager.persistAndFlush(new MyEntity("Spring"));
+	 * 
+ * @param the entity type + * @param entity the entity to persist + * @return the persisted entity + */ + public E persistAndFlush(E entity) { + persist(entity); + flush(); + return entity; + } + + /** + * Merge the state of the given entity into the current persistence context. Delegates + * to {@link EntityManager#merge(Object)} + * @param the entity type + * @param entity the entity to merge + * @return the merged entity + */ + public E merge(E entity) { + return getEntityManager().merge(entity); + } + + /** + * Remove the entity instance. Delegates to {@link EntityManager#remove(Object)} + * @param entity the entity to remove + */ + public void remove(Object entity) { + getEntityManager().remove(entity); + } + + /** + * Find by primary key. Delegates to {@link EntityManager#find(Class, Object)}. + * @param the entity type + * @param entityClass the entity class + * @param primaryKey the entity primary key + * @return the found entity or {@code null} if the entity does not exist + * @see #getId(Object) + */ + public E find(Class entityClass, Object primaryKey) { + return getEntityManager().find(entityClass, primaryKey); + } + + /** + * Synchronize the persistence context to the underlying database. Delegates to + * {@link EntityManager#flush()}. + */ + public void flush() { + getEntityManager().flush(); + } + + /** + * Refresh the state of the instance from the database, overwriting changes made to + * the entity, if any. Delegates to {@link EntityManager#refresh(Object)}. + * @param the entity type + * @param entity the entity to refresh + * @return the refreshed entity + */ + public E refresh(E entity) { + getEntityManager().refresh(entity); + return entity; + } + + /** + * Clear the persistence context, causing all managed entities to become detached. + * Delegates to {@link EntityManager#clear()} + */ + public void clear() { + getEntityManager().clear(); + } + + /** + * Remove the given entity from the persistence context, causing a managed entity to + * become detached. Delegates to {@link EntityManager#detach(Object)}. + * @param entity the entity to detach. + */ + public void detach(Object entity) { + getEntityManager().detach(entity); + } + + /** + * Return the ID of the given entity. Delegates to + * {@link PersistenceUnitUtil#getIdentifier(Object)}. + * @param entity the source entity + * @return the ID of the entity or {@code null} + * @see #getId(Object, Class) + */ + public Object getId(Object entity) { + return this.entityManagerFactory.getPersistenceUnitUtil().getIdentifier(entity); + } + + /** + * Return the ID of the given entity cast to a specific type. Delegates to + * {@link PersistenceUnitUtil#getIdentifier(Object)}. + * @param the ID type + * @param entity the source entity + * @param idType the expected ID type + * @return the ID of the entity or {@code null} + * @see #getId(Object) + */ + @SuppressWarnings("unchecked") + public T getId(Object entity, Class idType) { + Object id = getId(entity); + Assert.isInstanceOf(idType, id, "ID missmatch"); + return (T) id; + } + + /** + * Return the underlying {@link EntityManager} that's actually used to perform all + * operations. + * @return the entity manager + */ + public final EntityManager getEntityManager() { + EntityManager manager = EntityManagerFactoryUtils + .getTransactionalEntityManager(this.entityManagerFactory); + Assert.state(manager != null, "No transactional EnitiyManager found"); + return manager; + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerAutoConfiguration.java new file mode 100644 index 0000000000..756dfea256 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.persistence.EntityManagerFactory; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto-configuration for {@link TestEntityManager}. + * + * @author Phillip Webb + * @see AutoConfigureTestEntityManager + */ +@Configuration +@ConditionalOnClass({ EntityManagerFactory.class }) +@AutoConfigureAfter(HibernateJpaAutoConfiguration.class) +class TestEntityManagerAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TestEntityManager testEntityManager( + EntityManagerFactory entityManagerFactory) { + return new TestEntityManager(entityManagerFactory); + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/package-info.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/package-info.java new file mode 100644 index 0000000000..f1121a689d --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/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. + */ + +/** + * Auto-configuration for Data JPA tests. + */ +package org.springframework.boot.test.autoconfigure.orm.jpa; diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java new file mode 100644 index 0000000000..658ac5e206 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link DataJpaTest}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@DataJpaTest +public class DataJpaTestIntegrationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Autowired + private TestEntityManager entities; + + @Autowired + private ExampleRepository repository; + + @Autowired + private DataSource dataSource; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void testEntityManager() throws Exception { + ExampleEntity entity = this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.flush(); + Object id = this.entities.getId(entity); + ExampleEntity found = this.entities.find(ExampleEntity.class, id); + assertThat(found.getName()).isEqualTo("spring"); + } + + @Test + public void testEntityManagerPersistAndGetId() throws Exception { + Long id = this.entities.persistAndGetId(new ExampleEntity("spring", "123"), + Long.class); + assertThat(id).isNotNull(); + } + + @Test + public void testRepository() throws Exception { + this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.persist(new ExampleEntity("boot", "124")); + this.entities.flush(); + ExampleEntity found = this.repository.findByReference("124"); + assertThat(found.getName()).isEqualTo("boot"); + } + + @Test + public void replacesDefinedDatasourceWithEmbeddedDefault() throws Exception { + String product = this.dataSource.getConnection().getMetaData() + .getDatabaseProductName(); + assertThat(product).isEqualTo("H2"); + } + + @Test + public void didNotInjectExampleComponent() throws Exception { + this.thrown.expect(NoSuchBeanDefinitionException.class); + this.applicationContext.getBean(ExampleComponent.class); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests.java new file mode 100644 index 0000000000..028a844325 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests.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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase.Replace; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link DataJpaTest}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.AUTO_CONFIGURED, connection = EmbeddedDatabaseConnection.HSQL) +public class DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredIntegrationTests { + + @Autowired + private TestEntityManager entities; + + @Autowired + private ExampleRepository repository; + + @Autowired + private DataSource dataSource; + + @Test + public void testRepository() throws Exception { + this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.persist(new ExampleEntity("boot", "124")); + this.entities.flush(); + ExampleEntity found = this.repository.findByReference("124"); + assertThat(found.getName()).isEqualTo("boot"); + } + + @Test + public void replacesAutoConfiguredDataSource() throws Exception { + String product = this.dataSource.getConnection().getMetaData() + .getDatabaseProductName(); + assertThat(product).startsWith("HSQL"); + } + + @Configuration + @EnableAutoConfiguration // Will auto-configure H2 + static class Config { + + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests.java new file mode 100644 index 0000000000..029810eb4f --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase.Replace; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link DataJpaTest}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.AUTO_CONFIGURED) +public class DataJpaTestWithAutoConfigureTestDatabaseReplaceAutoConfiguredWithoutOverrideIntegrationTests { + + @Autowired + private TestEntityManager entities; + + @Autowired + private ExampleRepository repository; + + @Autowired + private DataSource dataSource; + + @Test + public void testRepository() throws Exception { + this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.persist(new ExampleEntity("boot", "124")); + this.entities.flush(); + ExampleEntity found = this.repository.findByReference("124"); + assertThat(found.getName()).isEqualTo("boot"); + } + + @Test + public void usesDefaultEmbeddedDatabase() throws Exception { + String product = this.dataSource.getConnection().getMetaData() + .getDatabaseProductName(); + // @AutoConfigureTestDatabase would use H2 but HSQL is manually defined + assertThat(product).startsWith("HSQL"); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests.java new file mode 100644 index 0000000000..7d033335af --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests.java @@ -0,0 +1,84 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link DataJpaTest}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@DataJpaTest +@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.HSQL) +public class DataJpaTestWithAutoConfigureTestDatabaseReplaceExplicitIntegrationTests { + + @Autowired + private TestEntityManager entities; + + @Autowired + private ExampleRepository repository; + + @Autowired + private DataSource dataSource; + + @Test + public void testRepository() throws Exception { + this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.persist(new ExampleEntity("boot", "124")); + this.entities.flush(); + ExampleEntity found = this.repository.findByReference("124"); + assertThat(found.getName()).isEqualTo("boot"); + } + + @Test + public void replacesDefinedDatasourceWithExplicit() throws Exception { + // H2 is explicitly defined by HSQL is the override + String product = this.dataSource.getConnection().getMetaData() + .getDatabaseProductName(); + assertThat(product).startsWith("HSQL"); + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + @Bean + public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.H2); + return builder.build(); + } + + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests.java new file mode 100644 index 0000000000..bef5f4b949 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase.Replace; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link DataJpaTest}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +public class DataJpaTestWithAutoConfigureTestDatabaseReplaceNoneIntegrationTests { + + @Autowired + private TestEntityManager entities; + + @Autowired + private ExampleRepository repository; + + @Autowired + private DataSource dataSource; + + @Test + public void testRepository() throws Exception { + this.entities.persist(new ExampleEntity("spring", "123")); + this.entities.persist(new ExampleEntity("boot", "124")); + this.entities.flush(); + ExampleEntity found = this.repository.findByReference("124"); + assertThat(found.getName()).isEqualTo("boot"); + } + + @Test + public void usesDefaultEmbeddedDatabase() throws Exception { + // HSQL is explicitly defined and should not be replaced + String product = this.dataSource.getConnection().getMetaData() + .getDatabaseProductName(); + assertThat(product).startsWith("HSQL"); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleComponent.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleComponent.java new file mode 100644 index 0000000000..2c369fdc4f --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleComponent.java @@ -0,0 +1,29 @@ +/* + * 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.autoconfigure.orm.jpa; + +import org.springframework.stereotype.Component; + +/** + * Example component used with {@link DataJpaTest} tests. + * + * @author Phillip Webb + */ +@Component +public class ExampleComponent { + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleDataJpaApplication.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleDataJpaApplication.java new file mode 100644 index 0000000000..5331a2fba5 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleDataJpaApplication.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.autoconfigure.orm.jpa; + +import javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +/** + * Example {@link SpringBootApplication} used with {@link DataJpaTest} tests. + * + * @author Phillip Webb + */ +@SpringBootApplication +public class ExampleDataJpaApplication { + + @Bean + public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL); + return builder.build(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleEntity.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleEntity.java new file mode 100644 index 0000000000..2e0fa97fd1 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleEntity.java @@ -0,0 +1,56 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * Example entity used with {@link DataJpaTest} tests. + * + * @author Phillip Webb + */ +@Entity +public class ExampleEntity { + + @Id + @GeneratedValue + private Long id; + + private String name; + + private String reference; + + protected ExampleEntity() { + } + + public ExampleEntity(String name, String reference) { + super(); + this.name = name; + this.reference = reference; + } + + public String getName() { + return this.name; + } + + public String getReference() { + return this.reference; + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleRepository.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleRepository.java new file mode 100644 index 0000000000..a783333cee --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/ExampleRepository.java @@ -0,0 +1,30 @@ +/* + * 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.autoconfigure.orm.jpa; + +import org.springframework.data.repository.Repository; + +/** + * Example repository used with {@link DataJpaTest} tests. + * + * @author Phillip Webb + */ +public interface ExampleRepository extends Repository { + + ExampleEntity findByReference(String reference); + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerTests.java new file mode 100644 index 0000000000..965dc2489a --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManagerTests.java @@ -0,0 +1,241 @@ +/* + * 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.autoconfigure.orm.jpa; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceUnitUtil; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.orm.jpa.EntityManagerHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link TestEntityManager}. + * + * @author Phillip Webb + */ +public class TestEntityManagerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private EntityManagerFactory entityManagerFactory; + + @Mock + private EntityManager entityManager; + + @Mock + private PersistenceUnitUtil persistenceUnitUtil; + + private TestEntityManager testEntityManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.testEntityManager = new TestEntityManager(this.entityManagerFactory); + given(this.entityManagerFactory.getPersistenceUnitUtil()) + .willReturn(this.persistenceUnitUtil); + } + + @Test + public void createWhenEntityManagerIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("EntityManagerFactory must not be null"); + new TestEntityManager(null); + } + + @Test + public void persistAndGetIdShouldPersistAndGetId() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(null, 123); + Object result = this.testEntityManager.persistAndGetId(entity); + verify(this.entityManager).persist(entity); + assertThat(result).isEqualTo(123); + } + + @Test + public void persistAndGetIdForTypeShouldPersistAndGetId() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(null, 123); + Integer result = this.testEntityManager.persistAndGetId(entity, Integer.class); + verify(this.entityManager).persist(entity); + assertThat(result).isEqualTo(123); + } + + @Test + public void persistShouldPersist() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + TestEntity result = this.testEntityManager.persist(entity); + verify(this.entityManager).persist(entity); + assertThat(result).isSameAs(entity); + } + + @Test + public void persistWhenAlreadyHasIdShouldThrowException() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(123); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage( + "Entity " + TestEntity.class.getName() + " already has an ID"); + this.testEntityManager.persistAndGetId(entity, Integer.class); + } + + @Test + public void persistAndFlushShouldPersistAndFlush() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + TestEntity result = this.testEntityManager.persistAndFlush(entity); + verify(this.entityManager).persist(entity); + verify(this.entityManager).flush(); + assertThat(result).isSameAs(entity); + } + + @Test + public void persistFlushFindShouldPersistAndFlushAndFind() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + TestEntity found = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(null, 123); + given(this.entityManager.find(TestEntity.class, 123)).willReturn(found); + TestEntity result = this.testEntityManager.persistFlushFind(entity); + verify(this.entityManager).persist(entity); + verify(this.entityManager).flush(); + assertThat(result).isSameAs(found); + } + + @Test + public void mergeShouldMerge() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + given(this.entityManager.merge(entity)).willReturn(entity); + TestEntity result = this.testEntityManager.merge(entity); + verify(this.entityManager).merge(entity); + assertThat(result).isSameAs(entity); + } + + @Test + public void removeShouldRemove() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + this.testEntityManager.remove(entity); + verify(this.entityManager).remove(entity); + } + + @Test + public void findShouldFind() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + given(this.entityManager.find(TestEntity.class, 123)).willReturn(entity); + TestEntity result = this.testEntityManager.find(TestEntity.class, 123); + assertThat(result).isSameAs(entity); + } + + @Test + public void flushShouldFlush() throws Exception { + bindEntityManager(); + this.testEntityManager.flush(); + verify(this.entityManager).flush(); + } + + @Test + public void refreshShouldRefresh() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + this.testEntityManager.refresh(entity); + verify(this.entityManager).refresh(entity); + } + + @Test + public void clearShouldClear() throws Exception { + bindEntityManager(); + this.testEntityManager.clear(); + verify(this.entityManager).clear(); + } + + @Test + public void detachShouldDetach() throws Exception { + bindEntityManager(); + TestEntity entity = new TestEntity(); + this.testEntityManager.detach(entity); + verify(this.entityManager).detach(entity); + } + + @Test + public void getIdForTypeShouldGetId() throws Exception { + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(123); + Integer result = this.testEntityManager.getId(entity, Integer.class); + assertThat(result).isEqualTo(123); + } + + @Test + public void getIdForTypeWhenTypeIsWrongShouldThrowException() throws Exception { + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(123); + this.thrown.expectMessage("ID missmatch Object of class [java.lang.Integer] " + + "must be an instance of class java.lang.Long"); + this.testEntityManager.getId(entity, Long.class); + } + + @Test + public void getIdShouldGetId() throws Exception { + TestEntity entity = new TestEntity(); + given(this.persistenceUnitUtil.getIdentifier(entity)).willReturn(123); + Object result = this.testEntityManager.getId(entity); + assertThat(result).isEqualTo(123); + } + + @Test + public void getEntityManagerShouldGetEntityManager() throws Exception { + bindEntityManager(); + assertThat(this.testEntityManager.getEntityManager()) + .isEqualTo(this.entityManager); + } + + @Test + public void getEntityManagerWhenNotSetShouldThrowException() throws Exception { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("No transactional EnitiyManager found"); + this.testEntityManager.getEntityManager(); + } + + private void bindEntityManager() { + EntityManagerHolder holder = new EntityManagerHolder(this.entityManager); + TransactionSynchronizationManager.bindResource(this.entityManagerFactory, holder); + } + + static class TestEntity { + + } + +}