junit测试中的spring-data-jpa beanvalidation

在我最近的工作中,我使用了spring-data-jpa来利用提供的存储库。 当涉及到集成测试时,我无法配置(我假设)测试的spring上下文,并且作为结果beanvalidation在我的测试中不起作用。

我知道我可以注入validation器,并对我的注释进行unit testing,但事实并非如此。 我正在编写集成测试,并希望在支持DB的情况下测试存储库。

我准备了简单的项目来显示所有必要的项目文件。

当我运行测试时,2失败了,我不知道为什么,类路径上存在hibernatevalidation器。

Failed tests: insertWrongEmail(com.example.core.data.jpa.UserRepositoryTest): Expected ConstraintViolationException wasn't thrown. insertToShortPassword(com.example.core.data.jpa.UserRepositoryTest): Expected ConstraintViolationException wasn't thrown. 

[..]

 Apr 23, 2013 5:00:08 PM org.hibernate.validator.internal.util.Version  INFO: HV000001: Hibernate Validator 4.3.1.Final 

源代码和mvn测试输出如下。

预先感谢您的帮助。

Java类(我删除了注释,geters,seters,equals,hashCode,toString等):

BaseEntity

 package com.example.core.data.jpa; import javax.persistence.*; @MappedSuperclass public abstract class BaseEntity { public static final String SEQUENCE = "global_seq"; @Id @SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE) @Column(name = "id") private Long id; public Long getId() { return id; } } 

用户

 package com.example.core.data.jpa; import javax.persistence.*; import javax.validation.constraints.Pattern; import java.io.Serializable; import java.util.List; @Entity @Table(name = "users") @NamedQuery(name = "User.findByEmail", query = "select u from User u where upper(u.email) = :email") public class User extends BaseEntity implements Serializable { private static final long serialVersionUID = 333700989750828624L; private static final int MAX_NAME_LENGTH = 128; private static final int MAX_EMAIL_LENGTH = 128; private static final int MAX_PASSWORD_LENGTH = 256; private static final int MIN_NAME_LENGTH = 6; private static final int EQUALS_MAGIC = 71; @Size(min = MIN_NAME_LENGTH) @Column(name = "name", nullable = false, length = MAX_NAME_LENGTH) private String name; @Pattern(regexp = "^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\\w]*[0-9a-zA-Z]\\.)+[a-zA-Z]{2,9})$") @Column(name = "email", nullable = false, unique = true, length = MAX_EMAIL_LENGTH) private String email; @Column(name = "password", nullable = false, length = MAX_PASSWORD_LENGTH) private String password; @OneToMany(mappedBy="user", cascade={CascadeType.ALL}, fetch = FetchType.EAGER) private List roles; //geters, seters, equals, hashCode } 

角色

 package com.example.core.data.jpa; import javax.persistence.*; import java.io.Serializable; @Entity @Table(name = "roles") public class Role extends BaseEntity implements Serializable { private static final long serialVersionUID = -2575871700366265974L; private static final int MAX_NAME_LENGTH = 128; @Column(name = "name", nullable = false, unique = true, length = MAX_NAME_LENGTH) private String name; @ManyToOne(cascade={CascadeType.ALL}) private User user; //geters, seters, equals, hashCode } 

UserRepositoryTest

 package com.example.core.data.jpa; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; import com.example.core.data.repository.UserRepository; import javax.inject.Inject; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import static org.junit.Assert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:test-context.xml") @Transactional public class UserRepositoryTest { @Inject private UserRepository userRepository; private User user; private static final String NAME = "User Name"; private static final String EMAIL = "user_name@example.com"; private static final String PASSWORD = "PASSWORD"; @Before public void setUp() { user = new User(NAME, EMAIL, PASSWORD); } @Test public void insert() { //given //when User inserted = userRepository.save(user); //then assertEquals(NAME, inserted.getName()); assertEquals(EMAIL, inserted.getEmail()); assertEquals(PASSWORD, inserted.getPassword()); assertNotNull(inserted.getId()); } @Test public void insertWrongEmail() { //given user.setEmail("NOT_AN_EMAIL_ADDRESS"); //when try { userRepository.save(user); fail("Expected ConstraintViolationException wasn't thrown."); } catch (ConstraintViolationException e) { //then assertEquals(1, e.getConstraintViolations().size()); ConstraintViolation violation = e.getConstraintViolations().iterator().next(); assertEquals("email", violation.getPropertyPath().toString()); assertEquals(Pattern.class, violation.getConstraintDescriptor() .getAnnotation().annotationType()); } } @Test public void insertToShortName() { //given user.setName("SHORT"); //when try { userRepository.save(user); fail("Expected ConstraintViolationException wasn't thrown."); } catch (ConstraintViolationException e) { //then assertEquals(1, e.getConstraintViolations().size()); ConstraintViolation violation = e.getConstraintViolations().iterator().next(); assertEquals("name", violation.getPropertyPath().toString()); assertEquals(Size.class, violation.getConstraintDescriptor() .getAnnotation().annotationType()); } } } 

UserRepository

 package com.example.core.data.repository; import org.springframework.data.repository.PagingAndSortingRepository; import com.example.core.data.jpa.User; public interface UserRepository extends PagingAndSortingRepository { } 

的pom.xml

  4.0.0 com.example data-jpa 1.0-SNAPSHOT jar Exampl core jpa package  UTF-8    org.hibernate.javax.persistence hibernate-jpa-2.0-api 1.0.1.Final   org.hibernate.java-persistence jpa-api 2.0-cr-1   org.hibernate hibernate-entitymanager 4.2.0.Final   javax.validation validation-api 1.0.0.GA compile   org.hibernate hibernate-validator 4.3.1.Final runtime   org.slf4j slf4j-jdk14 1.6.0 runtime   org.codehaus.jackson jackson-mapper-asl 1.9.12 provided   javax.inject javax.inject 1   org.springframework.data spring-data-jpa 1.2.0.RELEASE   cglib cglib 2.2.2   org.springframework spring-test 3.2.2.RELEASE test   org.hsqldb hsqldb 2.2.9 test   junit junit 4.11 test    

测试的context.xml

              com.example.core.data.jpa         

mvn测试输出

 [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Example core jpa package 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ data-jpa --- [debug] execute contextualize [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] [INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ data-jpa --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ data-jpa --- [debug] execute contextualize [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] [INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ data-jpa --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-surefire-plugin:2.10:test (default-test) @ data-jpa --- [INFO] Surefire report directory: /home/[..]/data-jpa/target/surefire-reports ------------------------------------------------------- TESTS ------------------------------------------------------- Running com.example.core.data.jpa.UserRepositoryTest Apr 23, 2013 4:39:34 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [test-context.xml] Apr 23, 2013 4:39:35 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.GenericApplicationContext@3cd6ad74: startup date [Tue Apr 23 16:39:35 CEST 2013]; root of context hierarchy Apr 23, 2013 4:39:35 PM org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor  INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring Apr 23, 2013 4:39:35 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver#423dc560' of type [class org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:35 PM org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory initDatabase INFO: Creating embedded database 'dataSource' Apr 23, 2013 4:39:35 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'dataSource' of type [class org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:35 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'dataSource' of type [class org.springframework.jdbc.datasource.SimpleDriverDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:35 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'jpaAdapter' of type [class org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:35 PM org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean createNativeEntityManagerFactory INFO: Building JPA container EntityManagerFactory for persistence unit 'default' Apr 23, 2013 4:39:35 PM org.hibernate.annotations.common.Version  INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final} Apr 23, 2013 4:39:35 PM org.hibernate.Version logVersion INFO: HHH000412: Hibernate Core {4.2.0.Final} Apr 23, 2013 4:39:35 PM org.hibernate.cfg.Environment  INFO: HHH000206: hibernate.properties not found Apr 23, 2013 4:39:35 PM org.hibernate.cfg.Environment buildBytecodeProvider INFO: HHH000021: Bytecode provider name : javassist Apr 23, 2013 4:39:35 PM org.hibernate.ejb.Ejb3Configuration configure INFO: HHH000204: Processing PersistenceUnitInfo [ name: default ...] Apr 23, 2013 4:39:36 PM org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator instantiateExplicitConnectionProvider INFO: HHH000130: Instantiating explicit connection provider: org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider Apr 23, 2013 4:39:36 PM org.hibernate.dialect.Dialect  INFO: HHH000400: Using dialect: org.hibernate.dialect.HSQLDialect Apr 23, 2013 4:39:36 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory Apr 23, 2013 4:39:36 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory  INFO: HHH000397: Using ASTQueryTranslatorFactory Apr 23, 2013 4:39:36 PM org.hibernate.validator.internal.util.Version  INFO: HV000001: Hibernate Validator 4.3.1.Final Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute INFO: HHH000228: Running hbm2ddl schema update Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute INFO: HHH000102: Fetching database metadata Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute INFO: HHH000396: Updating schema Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO: HHH000262: Table not found: roles Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO: HHH000262: Table not found: users Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO: HHH000262: Table not found: roles Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO: HHH000262: Table not found: users Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO: HHH000262: Table not found: global_seq Apr 23, 2013 4:39:37 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute INFO: HHH000232: Schema update complete Apr 23, 2013 4:39:37 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'entityManagerFactory' of type [class org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:37 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:37 PM org.springframework.context.support.AbstractApplicationContext$BeanPostProcessorChecker postProcessAfterInitialization INFO: Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) Apr 23, 2013 4:39:37 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@69950b4: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,userRepository,org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor#0,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,dataSource,transactionManager,entityManagerFactory,jpaAdapter,validator,messageSource,messageInterpolator,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (1): transaction manager [org.springframework.orm.jpa.JpaTransactionManager@58eac93b]; rollback [true] Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [TestContext@2b98919b testClass = UserRepositoryTest, testInstance = com.example.core.data.jpa.UserRepositoryTest@2d7f6d79, testMethod = insert@UserRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@8ec3a45 testClass = UserRepositoryTest, locations = '{classpath:test-context.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (2): transaction manager [org.springframework.orm.jpa.JpaTransactionManager@58eac93b]; rollback [true] Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [TestContext@2b98919b testClass = UserRepositoryTest, testInstance = com.example.core.data.jpa.UserRepositoryTest@1fcf7061, testMethod = insertWrongEmail@UserRepositoryTest, testException = java.lang.AssertionError: Expected ConstraintViolationException wasn't thrown., mergedContextConfiguration = [MergedContextConfiguration@8ec3a45 testClass = UserRepositoryTest, locations = '{classpath:test-context.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (3): transaction manager [org.springframework.orm.jpa.JpaTransactionManager@58eac93b]; rollback [true] Apr 23, 2013 4:39:38 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [TestContext@2b98919b testClass = UserRepositoryTest, testInstance = com.example.core.data.jpa.UserRepositoryTest@168ee2a9, testMethod = insertToShortPassword@UserRepositoryTest, testException = java.lang.AssertionError: Expected ConstraintViolationException wasn't thrown., mergedContextConfiguration = [MergedContextConfiguration@8ec3a45 testClass = UserRepositoryTest, locations = '{classpath:test-context.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 3.712 sec << [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException Process finished with exit code 1 

解决了

在@gunnar回答之后,我在repository.save之后更改了测试以刷新持久性上下文,并且它按预期工作。

对UserRepository的更改

 //inject entity manager @PersistenceContext private EntityManager entityManager; //add flush after save in test method @Test public void insertWrongEmail() { //given user.setEmail("NOT_AN_EMAIL_ADDRESS"); //when try { userRepository.save(user); entityManager.flush(); fail("Expected ConstraintViolationException wasn't thrown."); 

解决方案2

在得到@andyb的推荐后,我已经切换到eclipselink并且测试工作没有刷新。 这是我将采用的解决方案,我认为切换实现比使用变通方法更好。

我设法在本地重现问题中描述的问题,虽然在添加了缺少的get / set函数和UserRepository类之后:-)

经过一番挖掘后,我发现JPA ConstraintViolation与Rollback和Hibernate在与Bean Validation API结合使用时没有遵循JPA规范存在两个问题?

两者似乎都认为Hibernate没有正确抛出ConstraintValidationException

第二个问题的结果是缺陷HHH-8028 – entityManager.persist没有抛出针对Hibernate引发的ConstraintValidationException 。

为了确认这是同一个问题,我切换到一个简单的@GeneratedValue并传递了insertWrongEmailinsertToShortPassword仍然失败,但我把它归结为密码字段上缺少的@Size(min = 6) 。 添加之后,所有测试都通过了。

恢复到已配置的@GeneratedValue然后我将Hibernate替换为EclipseLink持久性框架。 两项测试都通过了,这似乎证实了之前问题的发现。 我所做的改变是:

pom.xml更改按照EclipseLink站点上的描述添加jpa工件

  org.eclipse.persistence org.eclipse.persistence.jpa 2.4.0 compile  

test-context.xml更改

切换到EclipseLinkJpaVendorAdapter

  

添加eclipselink.weaving属性,详见EclipseLinkJpaVendorAdapter而不是HibernateJpaVendorAdapter问题

         

User.java更改

我需要添加一个默认的无参数构造函数。 您可能已经在完整的User.java类中拥有一个。

在执行存储库操作后刷新持久性上下文可能会起到作用。 JPA仅在与数据库同步时调用Bean Validation,即在提交或刷新期间。