DRY: Use JUnit @Rule Instead of Repeating Setup/@Before in Each Test
I was for a long time unhappy that DbUnit Express users have to create a @Before method in each test just to get the test database initialized. Fortunately since version 1.3.0 they don't need to do it anymore thanks to JUnit Rules (if you are not familiar with them, they are an alternative to @Before/@After and @BeforeClass/@AfterClass, read this rules introduction).
As true coders you are certainly annoyed by so many words so let get to the source code.
EmbeddedDbTesterRule (requires JUnit 4.9+):
Notes:
Notes:
As true coders you are certainly annoyed by so many words so let get to the source code.
Code
@Rule implementation
First of all you must implement the rule and you setup/tear down code in it. I have done this directly in the class that my users use so that they don't need two instance variables (the rule and the actual db tester).EmbeddedDbTesterRule (requires JUnit 4.9+):
public class EmbeddedDbTesterRule extends EmbeddedDbTester implements TestRule {
private class DbInitializer extends ExternalResource {
@Override protected void before() throws Throwable {
EmbeddedDbTesterRule.this.onSetup();
};
}
private final DbInitializer initializer = new DbInitializer();
/** Ignore - for internal use by JUnit's Rule handling. */
public final Statement apply(Statement statement, Description description) {
return initializer.apply(statement, description);
}
}
Notes:
- EmbeddedDbTester is the class that my users uses. To make it self-initializing I had to create a subclass which implements JUnit's TestRule
- The implementation is simplified by reusing one of the predefined rules, ExternalResource, and just delegating to it
- JUnit calls the apply method at different times, of them the @Before call is propagated to us via ExternalResource.before. We don't really need to understand what a Statement or Description is and what to do with it.
- Similarly we could execute some code after each test method or before/after each test class (in which case you'd need to use @ClassRule, I'm not sure how/whether it can be combined with @Rule)
Rule usage in a test class
EmbeddedDbTesterRuleTest:
public class EmbeddedDbTesterRuleTest {
@Rule public EmbeddedDbTesterRule testDb = new EmbeddedDbTesterRule();
/**
* onSetup should have been executed automatically thanks to the @Rule and thus
* our data should be in the DB.
*/
@Test
public void should_execute_onSetup_automatically() throws Exception {
testDb.createCheckerForSelect("select some_text from my_test_schema.my_test_table")
.withErrorMessage("No data found => onSetup wasn't executed as expected")
.assertRowCount(1)
.assertNext("EmbeddedDbTesterRuleTest data");
}
}
Notes:
- To use it, you must declare a public, non-static field annotated with @Rule and its declared type must implement TestRule (formerly MethodRule)
- You then use the instance as you would the original parent class (EmbeddedDbTester), you just don't need to initialize it in a @Before method because it is done automatically
Conclusion
@Rules are certainly a great thing that can simplify the life of test writers and test framework authors. I'm not really happy with its API, which is little to invasive (the field must be public, the declared type - not just the actual runtime value - must implement TestRule => the API of my class visible to my users is littered with a JUnit-related method) but it is still a pretty good thing.Sidenote: About DbUnit Express
If you haven't heard about DbUnit Express before - it is a thin wrapper around DbUnit (unit testing of code interacting with a database) intended to make starting with DB testing as quick and as easy as possible by introducing- Convention over configuration - automatically loads data set file of name derived from the test name if it exists, ...
- Sensible defaults - comes pre-configured for an embedded Derby database
- Convenience methods - such as getDataSource() - extremely useful for testing code using Spring JDBC
- Utilities - RowComparator for easy verification of data in a select, DatabaseCreator for DB initialization from a DDL, replaceDatabase(dataSet), clearTable(name) , JUnit 4 @Rule automatically invoking onSetup(), ...
- Improved error reporting
- The configuration of DbUnit that proved to be most flexible and error-proof - fully qualified table names, XML data set,...
- Sample .ddl and data set .xml files
For more information check dbunit-express home page.