my solution, a bit complicated but i needed it for an test framework :-)
do not be afraid of the german javadocs, the method names and bodies should be enough to get it
FIRST create Annotation to mark Class or method for database work (create table and/or insert statements)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SchemaImport {
/**
* Location der Schemadatei(en). Die Datei darf nur SQL Statements enthalten.
* Wird keine Location gesetzt, greift der Defaultwert.
* @return String
*/
String[] locationsBefore() default {"input/schemas/before.sql"};
/**
* Location der Schemadatei(en). Die Datei darf nur SQL Statements enthalten.
* Wird keine Location gesetzt, greift der Defaultwert.
* @return String
*/
String[] locationsAfter() default {"input/schemas/after.sql"};
/**
* Ein SchemaImport findet nur bei passender Umgebungsvariable statt, mit diesem
* Flag kann dieses Verhalten geändert werden.
* @return boolean
*/
boolean override() default false;
}
SECOND create listener which looks for the annotation, AbstractTestExecutionListener
is a Spring Framework Class from
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.test</artifactId>
<version>2.5.6</version>
</dependency>
public class SchemaImportTestExecutionListener extends AbstractTestExecutionListener
implements ApplicationContextAware {
/**
* Standard LOG Definition.
*/
private static final Logger LOG = LoggerFactory.getLogger(
SchemaImportTestExecutionListener.class);
/**
* Datasource Name - gemeint ist der Name der Datasource Bean bzw. die ID.
*/
private static final String DATASOURCE_NAME = "dataSource";
/**
* JDBC Template.
*/
private SimpleJdbcTemplate simpleJdbcTemplate;
/**
* Flag um festzustellen ob prepareTestInstance schon gerufen wurde.
*/
private boolean isAlreadyPrepared = false;
/**
* Standard Constructor, laut API von konkreten Implementierungen für
* TestexecutionListener erwartet, es geht aber auch ohne.
*/
public SchemaImportTestExecutionListener() {
}
/**
* Für jede Testklasse die mit der {@link SchemaImport} Annotation ausgezeichnet
* ist, wird ein entsprechender SchemaImport durchgeführt.
*
* Der SchemaImport findet pro Klasse exakt einmal statt. Diese Verhalten
* entspricht der BeforeClass
* Annotation von JUnit.
*
* Achtung mit Nutzung von Schemaimport auf Klassenebene ist kein
* Rollback möglich, stattdessen SchemaImport auf Methodenebene nutzen.
*
* @param testContext
* @throws java.lang.Exception
*/
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestClass(), SchemaImport.class);
if ((annotation != null) && !isAlreadyPrepared && (isPropertyOrOverride(annotation))) {
executeSchemaImports(testContext, annotation.locationsBefore(), true);
isAlreadyPrepared = true;
}
}
/**
* Für jede Testmethode mit {@link SchemaImport} werden die angegebenen
* Schema Dateien als SQL ausgeführt.
*
* @param testContext
* @throws java.lang.Exception
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
// nur für Methoden mit passender Annotation Schemaimport durchführen
final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestMethod(), SchemaImport.class);
if (annotation != null) {
executeSchemaImports(testContext, annotation.locationsBefore(), true);
}
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
// nur für Methoden mit passender Annotation Schemaimport durchführen
final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestMethod(), SchemaImport.class);
if (annotation != null) {
executeSchemaImports(testContext, annotation.locationsAfter(), false);
}
}
/**
* Prüfen ob passende Umgebungsvariable gesetzt wurde. Diese kann durch
* entsprechendes Setzen des Flags an der Annotation überschrieben werden.
* @return
*/
private boolean isPropertyOrOverride(SchemaImport annotation) {
String prop = System.getProperty(TYPEnviroment.KEY_ENV);
if (StringUtils.trimToEmpty(prop).equals(TYPEnviroment.EMBEDDED.getEnv())) {
LOG.info("Running SchemaImport, Enviroment is set:'" + prop + "'");
return true;
} else {
if (annotation.override()) {
LOG.warn(
"Running SchemaImport, although Enviroment is set:'" + prop + "'");
return true;
} else {
LOG.warn(
"Not Running SchemaImport cause neither Environment or SchemaImport.override are set.");
return false;
}
}
}
/**
* Hilfesmethode die eigentlichen SchemaImport kapselt.
*
* @param testContext
* @param locations
*/
private void executeSchemaImports(TestContext testContext, String[] locations, boolean checkLocations) {
// für jede Datei SchemaImport durchführen, korrekte Reihenfolge
// ist durch Entwickler zu gewährleisten
if (locations.length > 0) {
for (String location : locations) {
if (StringUtils.trimToNull(location) != null) {
if (isResourceExistant(location, checkLocations)) {
LOG.info("Executing Schema Location: '" + location + "'");
SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate(
testContext), new ClassPathResource(location),
false);
} else {
LOG.warn(
"Schema Location '" + location + "' for SchemaImport not found.");
}
} else {
throw new RuntimeException("SchemaImport with empty Locations in:'" + testContext.getTestClass().getSimpleName() + "'");
}
}
}
}
/**
*
* @param resource
* @return
*/
private boolean isResourceExistant(String resource, boolean checkLocations) {
try {
new ClassPathResource(resource).getInputStream();
return true;
} catch (IOException ex) {
if (checkLocations) {
throw new RuntimeException(ex);
} else {
return false;
}
}
}
/**
* Hilfsmethode um an ein JdbcTemplate heranzukommen.
*
* @param TestContext
* @return SimpleJdbcTemplate
*/
private SimpleJdbcTemplate getJdbcTemplate(TestContext context) {
if (this.simpleJdbcTemplate == null) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(getDataSource(
context));
}
return this.simpleJdbcTemplate;
}
/**
* Hilfsmethode um an die Datasource heranzukommen.
*
* @param testContext
* @return DataSource
*/
private DataSource getDataSource(TestContext testContext) {
return (DataSource) testContext.getApplicationContext().getBean(
DATASOURCE_NAME, DataSource.class);
}
/** {@inheritDoc} */
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
throw new UnsupportedOperationException("Not supported yet.");
}
}
THIRD add the listener to the test execution
@ContextConfiguration(locations = {"classpath*:spring/persistence/*.xml"})
@Transactional
@TestExecutionListeners({
TransactionalTestExecutionListener.class,
SchemaImportTestExecutionListener.class})
public abstract class AbstractAvHibernateTests extends AbstractAvTests {
/**
* SimpleJdbcTemplate für Subclasses verfügbar.
*/
@Autowired
protected SimpleJdbcTemplate simpleJdbcTemplate;
}
in use
@SchemaImport(locationsBefore={"schemas/spring-batch/2.0.0/schema-hsqldb.sql"})
public class FooTest extends AbstractAvHibernateTests {
}
its important to note - beware of thread problems while using testNg parallel testing, for this to work there should be some 'synchronized' markers for the getJdbcTemplate / dataSource Methods in the listener
ps:
the code for the test base class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/*.xml"})
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
LogDurationTestExecutionListener.class,
LogMethodNameTestExecutionListener.class})
public abstract class AbstractAvTests implements ApplicationContextAware {
/**
* Logger für Subclasses verfügbar.
*/
protected final Logger LOG = LoggerFactory.getLogger(getClass());
/**
* {@link ApplicationContext} für Subclasses verfügbar.
*/
protected ApplicationContext applicationContext;
/** {@inheritDoc } */
@Override
public final void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
LogDurationTestExecutionListener and LogMethodNameTestExecutionListener are custom listeners not provided by spring, but not needed for the schemaImport to work properly