2 * Copyright 2005 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.wamblee.test;
19 import java.sql.Connection;
20 import java.sql.PreparedStatement;
21 import java.sql.ResultSet;
22 import java.sql.SQLException;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.List;
27 import java.util.TreeMap;
29 import javax.sql.DataSource;
31 import junit.framework.TestCase;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.dbunit.DatabaseUnitException;
36 import org.dbunit.database.DatabaseConnection;
37 import org.dbunit.database.DatabaseSequenceFilter;
38 import org.dbunit.database.IDatabaseConnection;
39 import org.dbunit.dataset.FilteredDataSet;
40 import org.dbunit.dataset.IDataSet;
41 import org.dbunit.dataset.filter.ITableFilter;
42 import org.dbunit.operation.DatabaseOperation;
43 import org.hibernate.SessionFactory;
44 import org.jmock.cglib.MockObjectTestCase;
45 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
46 import org.springframework.beans.factory.config.BeanDefinition;
47 import org.springframework.beans.factory.support.RootBeanDefinition;
48 import org.springframework.context.ApplicationContext;
49 import org.springframework.context.support.ClassPathXmlApplicationContext;
50 import org.springframework.context.support.GenericApplicationContext;
51 import org.springframework.jdbc.core.JdbcTemplate;
52 import org.springframework.jdbc.datasource.DataSourceUtils;
53 import org.springframework.jdbc.datasource.DriverManagerDataSource;
54 import org.springframework.orm.hibernate3.HibernateTemplate;
55 import org.springframework.transaction.PlatformTransactionManager;
56 import org.springframework.transaction.TransactionDefinition;
57 import org.springframework.transaction.TransactionStatus;
58 import org.springframework.transaction.support.DefaultTransactionDefinition;
59 import org.springframework.transaction.support.TransactionCallback;
60 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
61 import org.springframework.transaction.support.TransactionTemplate;
62 import org.wamblee.general.BeanKernel;
63 import org.wamblee.persistence.hibernate.HibernateMappingFiles;
66 * Test case support class for spring tests.
68 public class SpringTestCase extends MockObjectTestCase {
70 private Log LOG = LogFactory.getLog(SpringTestCase.class);
73 * Session factory bean name.
75 private static final String SESSION_FACTORY = "sessionFactory";
78 * Data source bean name.
80 private static final String DATA_SOURCE = "dataSource";
83 * Transaction manager bean name.
85 private static final String TRANSACTION_MANAGER = "transactionManager";
88 * Name of the ConfigFileList bean that describes the Hibernate mapping
91 private static final String HIBERNATE_CONFIG_FILES = "hibernateMappingFiles";
96 private static final String SCHEMA_PATTERN = "%";
99 * List of (String) configuration file locations for spring.
101 private String[] _configLocations;
104 * Application context for storing bean definitions that vary on a test by
105 * test basis and cannot be hardcoded in the spring configuration files.
107 private GenericApplicationContext _parentContext;
110 * Cached spring application context.
112 private ApplicationContext _context;
114 public SpringTestCase(Class<? extends SpringConfigFiles> aSpringFiles,
115 Class<? extends HibernateMappingFiles> aMappingFiles) {
117 SpringConfigFiles springFiles = aSpringFiles.newInstance();
118 _configLocations = springFiles.toArray(new String[0]);
119 } catch (Exception e) {
120 fail("Could not construct spring config files class '" + aSpringFiles.getName() + "'");
123 // Register the Hibernate mapping files as a bean.
124 _parentContext = new GenericApplicationContext();
125 BeanDefinition lDefinition = new RootBeanDefinition(aMappingFiles);
126 _parentContext.registerBeanDefinition(HIBERNATE_CONFIG_FILES,
128 _parentContext.refresh();
133 * Gets the spring context.
135 * @return Spring context.
137 protected synchronized ApplicationContext getSpringContext() {
138 if ( _context == null ) {
139 _context = new ClassPathXmlApplicationContext(
140 (String[]) _configLocations, _parentContext);
141 assertNotNull(_context);
147 * @return Hibernate session factory.
149 protected SessionFactory getSessionFactory() {
150 SessionFactory factory = (SessionFactory) getSpringContext().getBean(SESSION_FACTORY);
151 assertNotNull(factory);
155 protected void setUp() throws Exception {
156 LOG.info("Performing setUp()");
160 _context = null; // make sure we get a new application context for every
164 .overrideBeanFactory(new TestSpringBeanFactory(getSpringContext()));
172 * @see junit.framework.TestCase#tearDown()
175 protected void tearDown() throws Exception {
179 LOG.info("tearDown() complete");
184 * @return Transaction manager
186 protected PlatformTransactionManager getTransactionManager() {
187 PlatformTransactionManager manager = (PlatformTransactionManager) getSpringContext()
188 .getBean(TRANSACTION_MANAGER);
189 assertNotNull(manager);
194 * @return Starts a new transaction.
196 protected TransactionStatus getTransaction() {
197 DefaultTransactionDefinition def = new DefaultTransactionDefinition();
198 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
200 return getTransactionManager().getTransaction(def);
204 * Returns the hibernate template for executing hibernate-specific
207 * @return Hibernate template.
209 protected HibernateTemplate getTemplate() {
210 HibernateTemplate template = (HibernateTemplate) getSpringContext().getBean(HibernateTemplate.class.getName());
211 assertNotNull(template);
216 * Flushes the session. Should be called after some Hibernate work and
217 * before JDBC is used to check results.
220 protected void flush() {
221 getTemplate().flush();
225 * Flushes the session first and then removes all objects from the Session
226 * cache. Should be called after some Hibernate work and before JDBC is used
230 protected void clear() {
232 getTemplate().clear();
236 * Evicts the object from the session. This is essential for the
237 * implementation of unit tests where first an object is saved and is
238 * retrieved later. By removing the object from the session, Hibernate must
239 * retrieve the object again from the database.
243 protected void evict(Object aObject) {
244 getTemplate().evict(aObject);
248 * Gets the connection.
250 * @return Connection.
252 public Connection getConnection() {
253 return DataSourceUtils.getConnection(getDataSource());
256 public void cleanDatabase() throws SQLException {
258 if (! isDatabaseConfigured() ) {
262 String[] tables = getTableNames();
265 IDatabaseConnection connection = new DatabaseConnection(
267 ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
268 IDataSet dataset = new FilteredDataSet(filter, connection
269 .createDataSet(tables));
271 DatabaseOperation.DELETE_ALL.execute(connection, dataset);
272 } catch (DatabaseUnitException e) {
273 SQLException exc = new SQLException(e.getMessage());
280 * @throws SQLException
282 public String[] getTableNames() throws SQLException {
284 List result = new ArrayList();
285 LOG.debug("Getting database table names to clean (schema: '"
286 + SCHEMA_PATTERN + "'");
288 ResultSet tables = getConnection().getMetaData().getTables(null,
289 SCHEMA_PATTERN, "%", new String[] { "TABLE" });
290 while (tables.next()) {
291 String table = tables.getString("TABLE_NAME");
292 // Make sure we do not touch hibernate's specific
293 // infrastructure tables.
294 if (!table.toLowerCase().startsWith("hibernate")) {
296 LOG.debug("Adding " + table
297 + " to list of tables to be cleaned.");
300 return (String[]) result.toArray(new String[0]);
305 * @throws SQLException
307 public void emptyTables(List aTableList) throws SQLException {
308 Iterator liTable = aTableList.iterator();
309 while (liTable.hasNext()) {
310 emptyTable((String) liTable.next());
316 * @throws SQLException
318 public void emptyTable(String aTable) throws SQLException {
319 executeSql("delete from " + aTable);
324 * @throws SQLException
326 public void dropTable(String aTable) throws SQLException {
327 executeQuery("drop table " + aTable);
331 * Executes an SQL statement within a transaction.
335 * @return Return code of the corresponding JDBC call.
337 public int executeSql(final String aSql) {
338 return executeSql(aSql, new Object[0]);
342 * Executes an SQL statement within a transaction. See
343 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
344 * supported argument types.
349 * Argument of the sql statement.
350 * @return Return code of the corresponding JDBC call.
352 public int executeSql(final String aSql, final Object aArg) {
353 return executeSql(aSql, new Object[] { aArg });
357 * Executes an sql statement. See
358 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
359 * supported argument types.
362 * SQL query to execute.
365 * @return Number of rows updated.
367 public int executeSql(final String aSql, final Object[] aArgs) {
368 Map results = executeTransaction(new TestTransactionCallback() {
369 public Map execute() throws Exception {
370 JdbcTemplate template = new JdbcTemplate(getDataSource());
371 int result = template.update(aSql, aArgs);
373 Map map = new TreeMap();
374 map.put("result", new Integer(result));
380 return ((Integer) results.get("result")).intValue();
384 * Executes a transaction with a result.
387 * Callback to do your transactional work.
390 public Object executeTransaction(TransactionCallback aCallback) {
391 TransactionTemplate lTemplate = new TransactionTemplate(
392 getTransactionManager());
393 return lTemplate.execute(aCallback);
397 * Executes a transaction without a result.
400 * Callback to do your transactional work. .
402 protected void executeTransaction(TransactionCallbackWithoutResult aCallback) {
403 TransactionTemplate template = new TransactionTemplate(
404 getTransactionManager());
405 template.execute(aCallback);
409 * Executes a transaction with a result, causing the testcase to fail if any
410 * type of exception is thrown.
413 * Code to be executed within the transaction.
416 public Map executeTransaction(final TestTransactionCallback aCallback) {
417 return (Map) executeTransaction(new TransactionCallback() {
418 public Object doInTransaction(TransactionStatus aArg) {
420 return aCallback.execute();
421 } catch (Exception e) {
422 // test case must fail.
424 throw new RuntimeException(e);
431 * Executes a transaction with a result, causing the testcase to fail if any
432 * type of exception is thrown.
435 * Code to be executed within the transaction.
437 public void executeTransaction(
438 final TestTransactionCallbackWithoutResult aCallback) {
439 executeTransaction(new TransactionCallbackWithoutResult() {
440 public void doInTransactionWithoutResult(TransactionStatus aArg) {
443 } catch (Exception e) {
444 // test case must fail.
445 throw new RuntimeException(e.getMessage(), e);
452 * Executes an SQL query within a transaction.
456 * @return Result set.
458 public ResultSet executeQuery(String aSql) {
459 return executeQuery(aSql, new Object[0]);
463 * Executes a query with a single argument. See
464 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
465 * supported argument types.
471 * @return Result set.
473 public ResultSet executeQuery(String aSql, Object aArg) {
474 return executeQuery(aSql, new Object[] { aArg });
478 * Executes a query within a transaction. See
479 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
480 * supported argument types.
485 * Arguments to the query.
486 * @return Result set.
488 public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
489 Map results = executeTransaction(new TestTransactionCallback() {
490 public Map execute() throws Exception {
491 Connection connection = getConnection();
493 PreparedStatement statement = connection.prepareStatement(aSql);
494 setPreparedParams(aArgs, statement);
496 ResultSet resultSet = statement.executeQuery();
497 TreeMap results = new TreeMap();
498 results.put("resultSet", resultSet);
504 return (ResultSet) results.get("resultSet");
508 * Sets the values of a prepared statement. See
509 * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
510 * supported argument types.
513 * Arguments to the prepared statement.
516 * @throws SQLException
518 private void setPreparedParams(final Object[] aArgs,
519 PreparedStatement aStatement) throws SQLException {
520 for (int i = 1; i <= aArgs.length; i++) {
521 setPreparedParam(i, aStatement, aArgs[i - 1]);
526 * Sets a prepared statement parameter.
529 * Index of the parameter.
531 * Prepared statement.
533 * Value Must be of type Integer, Long, or String. TODO extend
534 * with more types of values.
535 * @throws SQLException
537 private void setPreparedParam(int aIndex, PreparedStatement aStatement,
538 Object aObject) throws SQLException {
539 if (aObject instanceof Integer) {
540 aStatement.setInt(aIndex, ((Integer) aObject).intValue());
541 } else if (aObject instanceof Long) {
542 aStatement.setLong(aIndex, ((Integer) aObject).longValue());
543 } else if (aObject instanceof String) {
544 aStatement.setString(aIndex, (String) aObject);
546 TestCase.fail("Unsupported object type for prepared statement: "
547 + aObject.getClass() + " value: " + aObject
548 + " statement: " + aStatement);
552 private boolean isDatabaseConfigured() {
555 } catch (NoSuchBeanDefinitionException e ) {
562 * @return Returns the dataSource.
564 public DataSource getDataSource() {
565 DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(DATA_SOURCE);
572 * @throws SQLException
574 protected int getTableSize(String aTable) throws SQLException {
575 ResultSet resultSet = executeQuery("select * from " + aTable);
578 while (resultSet.next()) {
585 protected int countResultSet(ResultSet aResultSet) throws SQLException {
588 while (aResultSet.next()) {