Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring + TestNG not transactionally rollback

I'm using TestNG 6.9.9 to build-up a regression test environment. But encounter a problem which I have never met when using JUnit. In my mind, when finish each test cases, the change of each data would be automatically rollback by default if the test methods run in the same transaction context as what they call. But seems that it's not the truth, and I cannot find out if any mistake in my code. Please help me out. properties in pom.xml which indicates the frameworks' version

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <springframework.version>4.2.4.RELEASE</springframework.version>
    <hibernate.version>4.3.11.Final</hibernate.version>
<testng.version>6.9.9</testng.version>
</properties>

Obviously, they are all up-to-date.

My test class:

package com.noahwm.hkapp.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.testng.Assert;
import org.testng.annotations.Test;

import com.noahwm.hkapp.api.db.dao.AppUserDao;
import com.noahwm.hkapp.api.db.model.AppUser;
import com.noahwm.hkapp.api.service.AppUserService;

@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public class AppUserServiceTestNGTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private AppUserService appUserService;

  @Test
  @Rollback
  @Transactional
  public void testApp() {
    AppUser appUser = new AppUser();
    appUser.setAge(10);
    appUser.setGender("F");
    appUser.setMobilePhone("13219201034");
    appUser.setName("HKAPP Test");
    appUserService.createUser(appUser);
    String appUserId = appUser.getId();
    Assert.assertNotNull(appUserId);
  }
}

Created a entity instance, than call createUser() to save it to DB. According what I have done in JUnit, the data will automatically rollback even if I didn't put the @Rollback annotation in the front of the test method.

The structure of AppUser is:

package com.noahwm.hkapp.api.db.model;

import javax.persistence.Column;
import javax.persistence.Entity;

@Entity(name = "APP_USERS")
public class AppUser extends BaseDataModel {

  @Column(name = "NAME")
  private String name;

  @Column(name = "GENDER")
  private String gender;

  @Column(name = "AGE")
  private Integer age;

  @Column(name = "MOBILE_PHONE")
  private String mobilePhone;

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGender() {
    return gender;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public String getMobilePhone() {
    return mobilePhone;
  }

  public void setMobilePhone(String mobilePhone) {
    this.mobilePhone = mobilePhone;
  }

}

BaseDataModel.java

package com.noahwm.hkapp.api.db.model;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

import org.hibernate.annotations.GenericGenerator;

@MappedSuperclass
public class BaseDataModel {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
  @Column(name = "ID", unique = true, length = 36, nullable = false)
  protected String id;

  @Version
  @Column(name = "version")
  protected Integer version;

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public Integer getVersion() {
    return version;
  }
}

ApplicationContext-test.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:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    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/jee
            http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-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/util
            http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <bean
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
            <value>classpath:jdbc.test.properties</value>
        </property>
    </bean>

    <context:annotation-config />
    <context:component-scan base-package="com.noahwm.hkapp.api" />

    <aop:aspectj-autoproxy />

    <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
        lazy-init="true" />

    <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
        destroy-method="close">
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}" />
        <property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}" />
        <property name="partitionCount" value="${jdbc.partitionCount}" />
        <property name="acquireIncrement" value="${jdbc.acquireIncrement}" />
    </bean>

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.noahwm.hkapp.api.db.model</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.jdbc.batch_size">10</prop>
                <prop key="hibernate.jdbc.fetch_size">30</prop>
                <prop key="hibernate.default_batch_fetch_size">10</prop>
            </props>
        </property>
    </bean>

    <tx:annotation-driven transaction-manager="txManager"
        proxy-target-class="true" />
    <bean id="txManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

The transaction-manager is named “txManager”. AppUserService.java

package com.noahwm.hkapp.api.service;

import java.util.List;
import java.util.Map;

import com.noahwm.hkapp.api.db.dao.AppUserDao;
import com.noahwm.hkapp.api.db.model.AppUser;

public interface AppUserService {
  void createUser(AppUser user);

}

AppUserServiceImpl.java

package com.noahwm.hkapp.api.service.impl;

import java.util.List;
import java.util.Map;

import org.hibernate.criterion.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.noahwm.hkapp.api.db.dao.AppUserDao;
import com.noahwm.hkapp.api.db.model.AppUser;
import com.noahwm.hkapp.api.service.AppUserService;
import com.noahwm.hkapp.api.service.EntityService;
import com.noahwm.hkapp.utils.SimpleSearchCriteria;

@Service("AppUserService")
@Transactional(propagation=Propagation.REQUIRED)
public class AppUserServiceImpl extends EntityService implements AppUserService {

  private static final Logger logger = LoggerFactory.getLogger(AppUserServiceImpl.class);

  @Autowired
  private AppUserDao dao;
  @Override
  public void createUser(AppUser user) {
    logger.debug("Creating user with name {}", user.getName());
    dao.save(user);
  }
}

AppUserDao.java

package com.noahwm.hkapp.api.db.dao;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.noahwm.hkapp.api.db.model.AppUser;

@Repository
@Transactional(propagation=Propagation.REQUIRED)
public class AppUserDao extends BaseDao<AppUser> {
  public void testsRollBack(AppUser appUser) throws Exception{
    save(appUser);
  }
}

BaseDao.java

package com.noahwm.hkapp.api.db.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;

import com.noahwm.hkapp.api.db.model.BaseDataModel;
import com.noahwm.hkapp.utils.SimpleSearchCriteria;

class BaseDao<T extends BaseDataModel> {

  private Class<T> domainClass;

  @Resource(name = "sessionFactory")
  protected SessionFactory sessionFactory;

  protected SessionFactory getSessionFactory() {
    return sessionFactory;
  }

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

  @SuppressWarnings("unchecked")
  public Class<T> getDomainClass() {
    if (domainClass == null) {
      Type type = this.getClass().getGenericSuperclass();
      ParameterizedType parameterizedType = (ParameterizedType) type;
      domainClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }
    return domainClass;
  }

  protected Session getCurrentSession() {
    return getSessionFactory().getCurrentSession();
  }

  public Criteria createCriteria() {
    return getCurrentSession().createCriteria(getDomainClass());
  }

  public void save(T o) {
    getCurrentSession().save(o);
  }

  public void update(T o) {
    getCurrentSession().update(o);
  }

  public void saveOrUpdate(T o) {
    getCurrentSession().saveOrUpdate(o);
  }

  public Object merge(Object o) {
    return getCurrentSession().merge(o);
  }

  public void delete(T o) {
    getCurrentSession().delete(o);
  }

  public T deleteById(Serializable id) {
    T o = findById(id);
    getCurrentSession().delete(o);
    return o;
  }

  public void evict(Object o) {
    getCurrentSession().evict(o);
  }

  @SuppressWarnings("unchecked")
  public List<T> findAll() {
    return createCriteria().list();
  }

  @SuppressWarnings("unchecked")
  public T findById(Serializable o) {
    List<T> results = createCriteria().add(Restrictions.idEq(o)).list();
    if (results.isEmpty()) {
      return null;
    } else {
      return results.get(0);
    }
  }

  @SuppressWarnings("unchecked")
  public T load(Serializable o) {
    return (T) getCurrentSession().load(getDomainClass(), o);
  }

  @SuppressWarnings("unchecked")
  public List<T> findBy(Map<String, Object> propertyNameValues) {
    return createCriteria().add(Restrictions.allEq(propertyNameValues)).list();
  }

  @SuppressWarnings("unchecked")
  public List<T> findBy(String propertyName, Object value) {
    return createCriteria().add(Restrictions.eq(propertyName, value)).list();
  }

  @SuppressWarnings("unchecked")
  public List<T> find(SimpleSearchCriteria simpleSearchCriteria) {
    Criteria criteria = createCriteria();
    Iterator<Criterion> criterions = simpleSearchCriteria.iterator();
    while(criterions.hasNext()) {
      criteria.add(criterions.next());
    }
    for(Order o : simpleSearchCriteria.getOrders()) {
      criteria.addOrder(o);
    }
    if(simpleSearchCriteria.getFetchSize() != null) {
      criteria.setFetchSize(simpleSearchCriteria.getFetchSize());
    }
    if(simpleSearchCriteria.getFirstResult() != null) {
      criteria.setFirstResult(simpleSearchCriteria.getFirstResult());
    }
    if(simpleSearchCriteria.getMaxResults() != null) {
      criteria.setMaxResults(simpleSearchCriteria.getMaxResults());
    }
    if(simpleSearchCriteria.getTimeout() != null) {
      criteria.setTimeout(simpleSearchCriteria.getTimeout());
    }
    return criteria.list();
  }

  public T findFirst(SimpleSearchCriteria simpleSearchCriteria) {
    simpleSearchCriteria.setMaxResults(1);
    List<T> results = find(simpleSearchCriteria);
    if(results.isEmpty()) {
      return null;
    } else {
      return results.get(0);
    }
  }

  public Object callNamedQuery(String sql, Map<String, Object> parameter) {
    Query query = getCurrentSession().createSQLQuery(sql);
    for(Entry<String, Object> entry:parameter.entrySet()){
      query.setParameter(entry.getKey(), entry.getValue());
    }
    return query.executeUpdate();
  }
}

Here is the DB init script:

CREATE TABLE "APP_USERS" (
"ID" VARCHAR(36),
"NAME" VARCHAR(50),
"GENDER" VARCHAR(1),
"AGE" NUMERIC(3,0),
"MOBILE_PHONE" VARCHAR(20),
    VERSION INTEGER)

As you see, it's a very common Spring TestNG integration test. But the auto rollback function cannot be used which bordered me a lot.

like image 931
Jomy Avatar asked Mar 14 '23 19:03

Jomy


1 Answers

Thanks to M. Deinum. To solve my problem, I just replace the class AbstractTestNGSpringContextTests with AbstractTransactionalTestNGSpringContextTests.

package com.noahwm.hkapp.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.testng.Assert;
import org.testng.annotations.Test;

import com.noahwm.hkapp.api.db.dao.AppUserDao;
import com.noahwm.hkapp.api.db.model.AppUser;
import com.noahwm.hkapp.api.service.AppUserService;

@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public class AppUserServiceTestNGTest extends AbstractTransactionalTestNGSpringContextTests {

  @Autowired
  private AppUserService appUserService;

  @Test
  @Rollback
  @Transactional
  public void testApp() {
    AppUser appUser = new AppUser();
    appUser.setAge(10);
    appUser.setGender("F");
    appUser.setMobilePhone("13219201034");
    appUser.setName("HKAPP Test");
    appUserService.createUser(appUser);
    String appUserId = appUser.getId();
    Assert.assertNotNull(appUserId);
  }
}
like image 140
Jomy Avatar answered Mar 24 '23 18:03

Jomy