Core Java

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.

Eyal Golan

Eyal is a professional software engineer and an architect. He is a developer and leader of highly sophisticated systems in different areas, such as networking, security, commerce and more.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
LK
LK
9 years ago

DbCleanupRule and MongoCleanupRule should be named in the same way… making a constructor for a class without name maching… Please, review your code

Eyal
9 years ago
Reply to  LK

You are correct and thanks for letting me know.
I have fixed in in gist:
https://gist.github.com/eyalgo/18d2a613fdeb220a8003

Back to top button