JUnit Rules
Introduction
In this post I would like to show an example of how to use JUnit Rule to make testing easier.
Recently I inherited a rather complex system, which not everything is tested. And even the tested code is complex. Mostly I see lack of test isolation. (I will write a different blog about working with Legacy Code).
One of the test (and code) I am fixing actually tests several components together. It also connect to the DB. It tests some logic and intersection between components. When the code did not compile in a totally different location, the test could not run because it loaded all Spring context. The structure was that before testing (any class) all Spring context was initiated. The tests extend BaseTest, which loads all Spring context.
BaseTest also cleans the DB in the @After method.
Important note: This article is about changing tests, which are not structured entirely correct. When creating new code and tests they should be isolated, testi one thing etc. Better tests should use mock DB / dependencies etc. After I fix the test and refactor, I’ll have confidence making more changes.
Back to our topic…
So, what I got is slow run of the test suit, no isolation and even problem running tests due to unrelated problems. So I decided separating the context loading with DB connection and both of them from the cleaning up of the database.
Approach
In order to achieve that I did three things: The first was to change inheritance of the test class. It stopped inheriting BaseTest. Instead it inherits AbstractJUnit4SpringContextTests Now I can create my own context per test and not load everything.
Now I needed two rules, a @ClassRule and @Rule @ClassRule will be responsible for DB connection @Rule will cleanup the DB after / before each test.
But first, what are JUnit Rules?
A short explanation would be that they provide a possibility to intercept test method, similar to AOP concept. @Rule allows us to intercept method before and after the actual run of the method. @ClassRule intercepts test class run. A very known @Rule is JUnit’s TemporaryFolder.
(Similar to @Before, @After and @BeforeClass).
Creating @Rule
The easy part was to create a Rule that cleanup the DB before and after a test method. You need to implement TestRule, which has one method: Statement apply(Statement base, Description description); You can do a-lot with it. I found out that usually I will have an inner class that extends Statement. The rule I created did not create the DB connection, but got it in the constructor.
Here’s the full code:
public class DbCleanupRule implements TestRule { private final DbConnectionManager connection; public MongoCleanupRule(DbConnectionManager connection) { this.connection = connection; } @Override public Statement apply(Statement base, Description description) { return new MongoCleanupStatement(base, connection); } private static final class DbCleanupStatement extends Statement { private final Statement base; private final DbConnectionManager connection; private MongoCleanupStatement(Statement base, DbConnectionManager connection) { this.base = base; this.connection = connection; } @Override public void evaluate() throws Throwable { try { cleanDb(); base.evaluate(); } finally { cleanDb(); } } private void cleanDb() { connection.doTheCleanup(); } } }
Creating @ClassRule
ClassRule is actually also TestRule. The only difference from Rule is how we use it in our test code. I’ll show it below.
The challenge in creating this rule was that I wanted to use Spring context to get the correct connection.
Here’s the code:
(ExternalResource is TestRule)
public class DbConnectionRule extends ExternalResource { private DbConnectionManager connection; public DbConnectionRule() { } @Override protected void before() throws Throwable { ClassPathXmlApplicationContext ctx = null; try { ctx = new ClassPathXmlApplicationContext("/META-INF/my-db-connection-TEST-ctx.xml"); mongoDb = (DbConnectionManager) ctx.getBean("myDbConnection"); } finally { if (ctx != null) { ctx.close(); } } } @Override protected void after() { } public DbConnectionManager getDbConnecttion() { return connection; } }
(Did you see that I could make DbCleanupRule inherit ExternalResource?)
Using it
The last part is how we use the rules. A @Rule must be public field. A @ClassRule must be public static field.
And there it is:
@ContextConfiguration(locations = { "/META-INF/one-dao-TEST-ctx.xml", "/META-INF/two-TEST-ctx.xml" }) public class ExampleDaoTest extends AbstractJUnit4SpringContextTests { @ClassRule public static DbCleanupRule connectionRule = new DbCleanupRule (); @Rule public DbCleanupRule dbCleanupRule = new DbCleanupRule(connectionRule.getDbConnecttion()); @Autowired private ExampleDao classToTest; @Test public void foo() { } }
That’s all.
Hope it helps.
Reference: | JUnit Rules from our JCG partner Eyal Golan at the Learning and Improving as a Craftsman Developer blog. |
DbCleanupRule and MongoCleanupRule should be named in the same way… making a constructor for a class without name maching… Please, review your code
You are correct and thanks for letting me know.
I have fixed in in gist:
https://gist.github.com/eyalgo/18d2a613fdeb220a8003