facebook

No Rollback after JUnit test

  1. MyEclipse IDE
  2.  > 
  3. Spring Development
Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
    Posts
  • #317413 Reply

    Hi all,

    I’m writing for the first time a JUnit test with MyEclipse4Spring and I’m probably doing something wrong.
    The transaction isn’t rolled back and the stored objects remain in database.

    Here’s an part of my Test class :

    
    @RunWith(SpringJUnit4ClassRunner.class)
    @TestExecutionListeners({
            DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class,
            TransactionalTestExecutionListener.class })
    @ContextConfiguration(locations = {
            "file:./src/main/resources/AAA-security-context.xml",
            "file:./src/main/resources/AAA-service-context.xml",
            "file:./src/main/resources/AAA-dao-context.xml",
            "file:./src/main/resources/AAA-web-context.xml" })
    @Transactional
    public class QuestionnaireServiceTest {
    
        private transient Logger logger = LoggerFactory.getLogger(getClass());
    
        /**
         * The Spring application context.
         *
         */
        @SuppressWarnings("unused")
        private ApplicationContext context;
    
        /**
         * The service being tested, injected by Spring.
         *
         */
        @Autowired
        protected QuestionnaireService service;
    
        /**
         * The questionnaireDAO injected by Spring.
         *
         */
        @Autowired
        protected QuestionnaireDAO dao;
    
        /**
         * Instantiates a new QuestionnaireServiceTest.
         *
         */
        public QuestionnaireServiceTest() {
            setupRequestContext();
        }
    
        /**
         * Operation Unit Test
         * Create a new Questionnaire with associated fields
         * 
         */
        @Test
        @Transactional
        @Rollback(true)
        public void testSaveQuestionnaire() throws Exception {
            Questionnaire questionnaire = new Questionnaire();
            questionnaire.setName("Test JUnit");
    
            // Test questionnaire creation
            Assert.assertNull(questionnaire.getId());
            questionnaire = service.saveQuestionnaire(questionnaire, null);
            Assert.assertNotNull(questionnaire.getId());
        }
    
        /**
         * Autowired to set the Spring application context.
         *
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Autowired
        public void setContext(ApplicationContext context) {
            
            /*** [SDE] Mock user authentication ***/
            String credentials = "pass";
            
            Collection coll = new ArrayList();
            coll.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
            
            User principal = new User("[email protected]", credentials, true, true, true, true, coll);
            TestingAuthenticationToken token = new TestingAuthenticationToken(principal, credentials);
            
         // Override the regular spring configuration 
            ProviderManager providerManager = (ProviderManager) context.getBean("authenticationManager");
            List list = new ArrayList();
            list.add(new TestingAuthenticationProvider());
            providerManager.setProviders(list);
            
            // Create and store the Acegi SecureContext into the ContextHolder. 
            SecurityContextImpl secureContext = new SecurityContextImpl();
            secureContext.setAuthentication(token);
            SecurityContextHolder.setContext(secureContext);
            /*** [SDE] End mock user authentication ***/
            
            ((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()).registerScope("session", new SessionScope());
            ((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()).registerScope("request", new RequestScope());
        }
    
        /**
         * Sets Up the Request context
         *
         */
        private void setupRequestContext() {
            MockHttpServletRequest request = new MockHttpServletRequest();
            ServletRequestAttributes attributes = new ServletRequestAttributes(request);
            RequestContextHolder.setRequestAttributes(attributes);
        }
    }
    

    And my tested function :

    
    /**
         * DWR : Insert or update questionnaire with associated fields.
         * 
         * @throws Exception 
         * @param questionnaire
         * @param fields
         * 
         */
        @Transactional
        public Questionnaire saveQuestionnaire(Questionnaire questionnaire, ArrayList<Field> fields) throws Exception {
            
            User currentUser;
            Calendar currentDate = Calendar.getInstance();
            try {
                currentUser = userService.getCurrentUser();
            } catch (UserNotLoggedException unle) {
                throw new UserNotLoggedException("Vous avez été déconnecté du serveur. Veuillez rafraîchir la page.");
            } catch (Exception e) {
                throw new Exception(e.getMessage());
            }
    
            questionnaire.setUpdater(currentUser);
            // Create questionnaire
            if (questionnaire.getId() == null) {
                questionnaire.setCreation(currentDate);
            // Update questionnaire
            } else {
                Questionnaire oldQuestionnaire = questionnaireDAO.findQuestionnaireByPrimaryKey(questionnaire.getId());
                questionnaire.setCreation(oldQuestionnaire.getCreation());
            }
            questionnaire = questionnaireDAO.store(questionnaire);
            questionnaireDAO.flush();
    
            return questionnaire;
        }
    

    All assertions pass and I get no error.
    Did I do something wrong ?

    Thanks.

    #317460 Reply

    jkennedy
    Member

    Which Database are you using?

    Thanks,
    Jack

    #317481 Reply

    Hi Jack,

    I’m using a MySQL 5.5.10 database (InnoDB) with driver mysql-connector-java.5.1.6-bin.jar
    For information (maybe usefull), I’m on MacOSX.

    Thanks,
    Sébastien.

    #317704 Reply

    jayperkins
    Member

    It appears that there may be a bug with the org.apache.commons.dbcp.BasicDataSource that is configured as the data source in your {project-name}-dao-context.xml:

    <bean name=”OracleDS” class=”org.apache.commons.dbcp.BasicDataSource” destroy-method=”close” >
    <property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
    <property name=”username” value=”${Oracle.connection.username}” />
    <property name=”password” value=”${Oracle.connection.password}” />
    <property name=”url” value=”${Oracle.connection.url}” />
    <property name=”maxIdle” value=”${Oracle.minPoolSize}” />
    <property name=”maxActive” value=”${Oracle.maxPoolSize}” />
    </bean>

    I have seen rollbacks ignored with oracle, mysql and derby using this data source both in the junit tests and at runtime. I can’t say if the bug is actually with the BasicDataSource or if it is that data source in combination with the spring transaction annotations.

    There are a couple of workarounds that you can try:
    1) Use the atomikos non xa data source instead – so your configuration would look like this:
    <bean name=”OracleDS” class=”com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean” >
    <property e=”uniqueResourceName” value=”OracleDS__l-u7AJQFEd-0wrWK7_AOqg” />
    <property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
    <property name=”user” value=”${Oracle.connection.username}” />
    <property name=”password” value=”${Oracle.connection.password}” />
    <property name=”url” value=”${Oracle.connection.url}” />
    <property name=”minPoolSize” value=”${Oracle.minPoolSize}” />
    <property name=”maxPoolSize” value=”${Oracle.maxPoolSize}” />
    </bean>

    This workaround has had the most testing exposure, so it would probably be your best bet.

    2) Use the org.apache.commons.dbcp.managed.BasicManagedDataSource instead of the BasicDataSource – so your configuration would look like this:
    <bean name=”OracleDS” class=”org.apache.commons.dbcp.managed.BasicManagedDataSource” destroy-method=”close” >
    <property name=”transactionManager” ref=”atomikosTransactionManager” />
    <property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
    <property name=”username” value=”${Oracle.connection.username}” />
    <property name=”password” value=”${Oracle.connection.password}” />
    <property name=”url” value=”${Oracle.connection.url}” />
    <property name=”maxIdle” value=”${Oracle.minPoolSize}” />
    <property name=”maxActive” value=”${Oracle.maxPoolSize}” />
    </bean>

    This workaround will probably work, because it appears that the fact that it takes a javax.transaction.TransactionManager allows it to enlist resources in the transaction so that the rollback semantics are honored.

    3) Remove the calls to the dao.flush() methods in your service implementation. Removing those calls allowed the rollback semantics to occur as expected. I am not exactly sure why.

    This is the least sure workaround, because I don’t know if the BasicDataSource has other problems beside the flush() call appearing to auto commit.

    In any event, we will be fixing this for the next release, which is due out soon.

    Let me know if any of the workarounds do not actually work for you or if you have other questions.

    Thanks,

    Jay

    #317733 Reply

    Hi Jack,

    I used the first solution and the rollback is correctly done.
    Thanks again for your expertise.

    Sébastien.

    #317744 Reply

    jkennedy
    Member

    Glad the solution worked, Jay Perkins provided this detail, much appreciation Jay…

    Jack

Viewing 6 posts - 1 through 6 (of 6 total)
Reply To: No Rollback after JUnit test

You must be logged in to post in the forum log in