0

I can't figure out how to make my integration tests rollback. If I insert, run tests, then query, I always get an empty table at the end.

  1. I insert with: INSERT INTO POKEMON_LOCATION VALUES (1, 2, 3);
  2. I run PokemonLocationDaoIT
  3. I query with SELECT * FROM POKEMON_LOCATION

Some code:

PokemonLocationDaoIT.java

package com.uprr.app.tng.spring.dao;

import com.uprr.app.tng.spring.config.DefaultDaoConfig;
import com.uprr.app.tng.spring.pojo.PokemonLocation;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.sql.DataSource;
import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Created by david on 9/17/16.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DefaultDaoConfig.class)
@Transactional
@Rollback
public class PokemonLocationDaoIT {
    private static final Operation DELETE_POKEMON_LOCATION = deleteAllFrom("POKEMON_LOCATION");

    @Inject private PokemonLocationDao testable;

    @Test
    public void shouldBeEmpty() throws Exception {
        this.testable.deleteAll();
        final Collection<PokemonLocation> all = this.testable.getAll();
        assertThat(all).isEmpty();
    }
}

DefaultPokemonLocationDao

package com.uprr.app.tng.spring.dao;

import com.uprr.app.tng.spring.exception.InvalidPrimaryKeyException;
import com.uprr.app.tng.spring.pojo.PokemonLocation;
import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.annotation.Nonnull;
import java.sql.SQLException;
import java.util.Collection;

/**
 * Created by david on 9/17/16.
 */
public class DefaultPokemonLocationDao implements PokemonLocationDao {
    @Nonnull private final JdbcTemplate               jdbcTemplate;
    @Nonnull private final RowCountMapper             rowCountMapper;
    @Nonnull private final RowMapper<PokemonLocation> rowMapper;

    public DefaultPokemonLocationDao(@Nonnull final JdbcTemplate jdbcTemplate,
                                     @Nonnull final RowCountMapper rowCountMapper,
                                     @Nonnull final RowMapper<PokemonLocation> rowMapper) {
        this.jdbcTemplate = jdbcTemplate;
        this.rowCountMapper = rowCountMapper;
        this.rowMapper = rowMapper;
    }

    @Override
    public void create(@Nonnull final PokemonLocation object) {
        final int id = 1; // FIXME: How to use PK generator
        this.jdbcTemplate.update("INSERT INTO POKEMON_LOCATION VALUES (ID = :id, X = :x, Y = :y)", id, object.getX(),
                                 object.getY());
    }

    @Override
    public PokemonLocation get(@Nonnull final Integer id) {
        return this.jdbcTemplate.queryForObject("SELECT * FROM POKEMON_LOCATION WHERE ID = :id", new Integer[]{id},
                                                this.rowMapper);
    }

    @Override
    public Collection<PokemonLocation> getAll() throws SQLException {
        return this.jdbcTemplate.query("SELECT * FROM POKEMON_LOCATION", this.rowMapper);
    }

    @Override
    public void update(@Nonnull final PokemonLocation location) {
        final Integer primaryKey = location.getPrimaryKey();
        if (null == primaryKey) {
            throw new InvalidPrimaryKeyException("Can not update an object without a primary key. Try the save() " +
                                                     "method for creating new rows. " + location);
        } else {
            final String sql         = "UPDATE POKEMON_LOCATION SET X = :x AND Y = :y WHERE ID = :id";
            final int    updatedRows = this.jdbcTemplate.update(sql, location.getX(), location.getY(), primaryKey);
            if (1 != updatedRows) {
                throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(sql, 1, updatedRows);
            }
        }
    }

    @Override
    public void delete(@Nonnull final Integer id) {
        final String sql         = "DELETE FROM POKEMON_LOCATION WHERE ID = :id";
        final int    updatedRows = this.jdbcTemplate.update(sql, id);
        if (1 != updatedRows) {
            throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(sql, 1, updatedRows);
        }
    }

    @Override
    public void delete(@Nonnull final PokemonLocation object) {
        final Integer pk = object.getPrimaryKey();
        if (null == pk) {
            throw new InvalidPrimaryKeyException("Cannot delete an object with a null primary key: " + object);
        } else {
            this.delete(pk);
        }
    }

    @Override
    public int deleteAll() {
        return this.jdbcTemplate.update("DELETE FROM POKEMON_LOCATION");
    }

    @Override
    public int count() {
        return this.jdbcTemplate.queryForObject("SELECT count(*) FROM POKEMON_LOCATION", this.rowCountMapper);
    }
}

DefaultDaoConfig.java

package com.uprr.app.tng.spring.config;

import com.uprr.app.tng.spring.dao.DefaultPokemonLocationDao;
import com.uprr.app.tng.spring.dao.PokemonLocationDao;
import com.uprr.app.tng.spring.dao.PokemonLocationMapper;
import com.uprr.app.tng.spring.dao.RowCountMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;

import javax.inject.Inject;
import javax.sql.DataSource;

/**
 * Created by david on 9/17/16.
 */
@Import(PropertiesConfig.class)
@EnableTransactionManagement
public class DefaultDaoConfig implements DaoConfig {
    @Inject private Environment environment;

    @Bean
    @Override
    public PokemonLocationDao pokemonLocationDao() {
        return new DefaultPokemonLocationDao(this.jdbcTemplate(), this.rowCountMapper(), this.pokemonLocationMapper());
    }

    @Bean
    public PokemonLocationMapper pokemonLocationMapper() {
        return new PokemonLocationMapper();
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(this.dataSource());
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(this.dataSource());
    }

    @Bean
    public DataSource dataSource() {
        final SQLiteDataSource dataSource = new SQLiteDataSource(new SQLiteConfig());
        dataSource.setUrl(this.environment.getRequiredProperty("database.jdbc.url"));
        return dataSource;
    }

    @Bean
    public RowCountMapper rowCountMapper() {
        return new RowCountMapper();
    }
}

The log shows that Spring is rolling the transaction back:

Sep 25, 2016 5:24:15 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
INFO: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
Sep 25, 2016 5:24:15 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
INFO: Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@64d2d351, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@1b68b9a4, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@4f9a3314, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3b2c72c2, org.springframework.test.context.transaction.TransactionalTestExecutionListener@491666ad, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@176d53b2]
Sep 25, 2016 5:24:15 PM org.springframework.context.support.GenericApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericApplicationContext@f8c1ddd: startup date [Sun Sep 25 17:24:15 CDT 2016]; root of context hierarchy
Sep 25, 2016 5:24:15 PM org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init>
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
Sep 25, 2016 5:24:19 PM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@2dd29a59 testClass = PokemonLocationDaoIT, testInstance = com.uprr.app.tng.spring.dao.PokemonLocationDaoIT@784c3487, testMethod = shouldBeEmpty@PokemonLocationDaoIT, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@1e636ea3 testClass = PokemonLocationDaoIT, locations = '{}', classes = '{class com.uprr.app.tng.spring.config.DefaultDaoConfig}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@53142455]; rollback [true]
Sep 25, 2016 5:24:35 PM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Rolled back transaction for test context [DefaultTestContext@2dd29a59 testClass = PokemonLocationDaoIT, testInstance = com.uprr.app.tng.spring.dao.PokemonLocationDaoIT@784c3487, testMethod = shouldBeEmpty@PokemonLocationDaoIT, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@1e636ea3 testClass = PokemonLocationDaoIT, locations = '{}', classes = '{class com.uprr.app.tng.spring.config.DefaultDaoConfig}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].

So can anyone tell me why the rows are deleted after running this test?

Let me know if there is more code that you might need to see.

N00b Pr0grammer
  • 4,503
  • 5
  • 32
  • 46
DavidZemon
  • 501
  • 5
  • 21
  • Possible duplicate of [this](http://stackoverflow.com/questions/9649318/how-can-i-config-to-turn-off-autocommit-in-spring-jdbc) question on SO? Hope this question answers your question! – N00b Pr0grammer Sep 26 '16 at 03:54
  • I wish it were that simple. However, as far as I can tell I'm using the same techniques as suggested by that answer. The line that was missing in that question was ``, which in Java config is replaced by `@EnableTransactionManagement`. And I have `@Transactional` on my unit test, as well as `@Rollback` so I don't think that's my issue. – DavidZemon Sep 26 '16 at 14:40

0 Answers0