Search code examples
javaspringtestcontext

@ContextConfiguration doesn't create transaction proxy


I'm trying create some kind of integration test environment and encountered with issue when Test Context Framework doesn't create transaction proxies for my beans. My code:

JUnit class: FileServiceImplTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
        @ContextConfiguration(value = "file:src/main/webapp/WEB-INF/spring/applicationContext-db.xml"),
        @ContextConfiguration(value = "file:src/main/webapp/WEB-INF/spring/applicationContext.xml")
})
public class FileServiceImplTest {

    @Autowired
    private FileService fileService;

    @Test
    public void testSaveFolder() {
        FolderDTO rootFolder = new FolderDTO();
        rootFolder.setName("ROOT");
        rootFolder.setParentId(null);
        rootFolder.setIdPath("/");
        rootFolder.setPath("/");

        fileService.saveFile(rootFolder);

        List<AbstractFileDTO> rootFiles = fileService.getRootFiles();

        assertEquals(1, rootFiles.size());

        AbstractFileDTO abstractFileDTO = rootFiles.get(0);

        assertEquals(rootFolder, abstractFileDTO);

    }

}

Test context frame work inject into 'fileService' filed the FileService bean itself, not transaction proxy. It is reason of exception:

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:132)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:697)
    at org.sbezgin.p2016.db.dao.impl.FileDAOImpl.getSession(FileDAOImpl.java:69)
    at org.sbezgin.p2016.db.dao.impl.FileDAOImpl.saveOrUpdateFile(FileDAOImpl.java:33)
    at org.sbezgin.p2016.services.file.impl.FileServiceImpl.saveFile(FileServiceImpl.java:41)
    at org.sbezgin.p2016.services.file.impl.FileServiceImplTest.testSaveFolder(FileServiceImplTest.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)

When I run this code under tomcat everything works fine, issue is appear only during running tests. Please help to fix this.

Rest of my code: FileServiceImpl

    @Transactional
public class FileServiceImpl implements FileService {
    private FileDAO fileDAO;
    private BeanTransformer beanTransformer;

    @Override
    public AbstractFileDTO getFileByID(long fileID) {
        return null;
    }

    @Override
    public FolderDTO getFolder(long folderID) {
        return null;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void saveFile(AbstractFileDTO file) {
        Long id = file.getId();
        if (id == null) {
            AbstractFile fileEntity = (AbstractFile) beanTransformer.transformDTOToEntity(file);
            User user = new User();
            user.setId(1);
            fileDAO.saveOrUpdateFile(user, fileEntity);
        }
    }

    @Override
    public void setPermission(long fileD, PermissionDTO perm) {

    }

    @Override
    public void renameFile(long fileID, String newName) {

    }

    @Override
    public void deleteFile(long fileID, boolean recursively) {

    }

    @Override
    public List<AbstractFileDTO> getRootFiles() {
        User user = new User();
        user.setId(1);
        List<AbstractFile> rootFiles = fileDAO.getRootFiles(user);
        List<AbstractFileDTO> abstractFileDTOs = new ArrayList<>(rootFiles.size());
        abstractFileDTOs.addAll(
                rootFiles.stream().map(
                        rootFile -> (AbstractFileDTO) beanTransformer.transformEntityToDTO(rootFile)
                ).collect(Collectors.toList())
        );
        return abstractFileDTOs;
    }

    @Override
    public List<AbstractFileDTO> getChildren(long folderID) {
        return null;
    }

    @Override
    public List<AbstractFileDTO> getFilesByType(String javaType) {
        return null;
    }


    public FileDAO getFileDAO() {
        return fileDAO;
    }

    public void setFileDAO(FileDAO fileDAO) {
        this.fileDAO = fileDAO;
    }

    public BeanTransformer getBeanTransformer() {
        return beanTransformer;
    }

    public void setBeanTransformer(BeanTransformer beanTransformer) {
        this.beanTransformer = beanTransformer;
    }
}

FileDAOImpl.java

   public class FileDAOImpl implements FileDAO {

    private SessionFactory sessionFactory;

    @Override
    public AbstractFile getFileByID(User user, long fileID) {
        return null;
    }

    @Override
    public Folder getFolder(User user, long folderID) {
        return null;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void saveOrUpdateFile(User user, AbstractFile file) {
        Session session = getSession();
        file.setClassName(file.getClass().getCanonicalName());
        file.setOwnerID(user.getId());
        session.save(file);
    }

    @Override
    public void saveOrUpdateFiles(User user, List<AbstractFile> files) {

    }

    @Override
    public void deleteFile(User user, long fileID, boolean recursively) {

    }

    @Override
    public List<AbstractFile> getRootFiles(User user) {
        Session session = getSession();
        Query query = session.createQuery("from AbstractFile as file where file.ownerID = :ownerId and file.parentId is null ");
        query.setParameter("ownerId", user.getId());
        List list = query.list();
        return list;
    }

    @Override
    public List<AbstractFile> getChildren(User user, long folderID) {
        return null;
    }

    @Override
    public List<AbstractFile> getFilesByType(User user, String javaType) {
        return null;
    }

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
                            http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <bean name="dozer" class="org.dozer.DozerBeanMapper" />

    <bean name="beanTransformer" class="org.sbezgin.p2016.services.impl.BeanTransformerImpl">
        <property name="dozerBeanMapper" ref="dozer"/>
        <property name="beanMap">
            <map>
                <entry key="org.sbezgin.p2016.db.dto.file.FolderDTO" value="org.sbezgin.p2016.db.entity.file.Folder"/>
            </map>
        </property>
    </bean>

    <!-- services -->
    <bean name="fileService" class="org.sbezgin.p2016.services.file.impl.FileServiceImpl">
        <property name="fileDAO" ref="fileDAO" />
        <property name="beanTransformer" ref="beanTransformer"/>
    </bean>

    <!-- dao -->
    <bean name="fileDAO" class="org.sbezgin.p2016.db.dao.impl.FileDAOImpl">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
</beans>

applicationContext-db.xml

    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
                            http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <context:property-placeholder location="WEB-INF/hibernate.properties" ignore-unresolvable="false"/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan">
            <list>
                <value>org.sbezgin.p2016.db.entity</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql:false}</prop>
                <prop key="hibernate.id.new_generator_mappings">${hibernate.id.new_generator_mappings}</prop>
                <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

hibernate.properties

    jdbc.driverClassName = org.hsqldb.jdbcDriver
    jdbc.url = jdbc:hsqldb:hsql://localhost:9001/xdb
    jdbc.username = sa
    jdbc.password =
    hibernate.dialect = org.hibernate.dialect.HSQLDialect
    hibernate.show_sql = true
hibernate.format_sql = true
hibernate.id.new_generator_mappings = false
hibernate.hbm2ddl.auto = create-drop

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.0.7.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- TEST artifacts -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>1.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.4.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.2.8</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>p2016</finalName>
        <plugins>
            <plugin>
                <!-- current version -->
                <groupId>fr.avianey.mojo</groupId>
                <artifactId>hsqldb-maven-plugin</artifactId>
                <version>1.0.0</version>

                <!--
                    default value for in memory jdbc:hsqldb:hsql://localhost/xdb
                    override only values you want to change
                -->
                <configuration>
                    <driver>org.hsqldb.jdbcDriver</driver>
                    <address>localhost</address>
                    <port>9001</port>
                    <name>xdb</name>
                    <username>sa</username>
                    <password></password>
                    <validationQuery>SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS</validationQuery>
                </configuration>

                <!-- call start and stop -->
                <executions>
                    <execution>
                        <id>start-hsqldb</id>
                        <phase>process-test-classes</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-hsqldb</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Solution

  • Based on Spring Framework's documentation:

    In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener which is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded via @ContextConfiguration semantics. In addition, you must declare Spring’s @Transactional annotation either at the class or method level for your tests.

    You did define a PlatformTransactionManager in your applicationContext-db.xml but did not add a @Transactional on your FileServiceImplTest class or testSaveFolder method. Add the @Transactional on your test class or methods.