Categories
Development

JUnit and ParameterResolver — Caching database connections in your tests

This is a re-post of an original Medium article. As I am moving my content here I will re-post some content.


Car speeding on the road

We aim for fast tests, ideally completing all tests within 30 seconds. Currently, our tests take 1 minute and 30 seconds, but we are determined to to get ther. 😃 To achieve this goal, we must reduce the overhead of each run.

Read on to learn about the techniques we use to speed up DB connection handling and migrations.


We aim to minimize the number of database tests we write by using fakes, but we still require some tests to verify our DB layer. We just don’t want the majority of our tests slowed with network access (even locally in Docker).

Establishing connections takes time, and the overhead of checking migrations before each test can also be time-consuming. We usually run a persistent DB in a Docker container, so we only have to create connections and migrate once for each run. We do use Test Containers if no DB is available, but it is slower. Every millisecond counts. 😃

Therefore we looked for a way to minimize this when running thousands of tests.

JUnit enforces strict isolation between tests so you can’t just inherit a class or something and get a shared value across the run. But as we knew Spring caches contexts across tests, there had to be a way. JUnit ParameterResolvers come to the rescue:

class DatabaseTestExtension : ParameterResolver {
private val STORE_NAME = "main-database"
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext?): Boolean {
return parameterContext.parameter.type == Database::class.java
}
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
// We do the store thing here to avoid loading and migrating the DB for each test/class
// Will however load per thread, so are not guaranteed to be done only once
val store = extensionContext.root.getStore(Namespace.create(DatabaseTestExtension::class.java.simpleName))
val db: Database = (store.get(STORE_NAME) as Database?) ?: Database(Config.load()).also {
// New object so do initialization and store
it.initializeAndMigrate()
store.put(STORE_NAME, it)
}
return db
}
}

The Database and Config objects are just custom wrappers around HikariJDBI and Liquibase. You can store the JDBI object or a Hikari connection pool directly by changing the code above and adjustinng the class type. The important part is putting it in the store so it is persisted across runs.

To use it you do something like this:

@Test
@ExtendWith(DatabaseTestExtension::class)
fun testSomething(db: Database) {
}

Pro tip: You can add the ExtendWith annotation to the entire test class, not just the test method.

And just like that you have a resource that won’t take extra time/load to run when running all your tests. 😃


Subscribe for further updates 🙂

Categories
Development

Quicker JUnit Spring context tests in Maven

This is probably fairly well known, but I couldn’t find any doc on it when I searched so I’ll put it up here.

First off: Don’t load your Spring Context in your tests unless you absolutely have to. Some integration tests should load it, but keep it to a minimum. Loading the context is expensive, especially if you load up Hibernate and maybe H2 in that context.

Avoid loading the context is important both for speed and design. I have seen way too many tests where the context is loaded just because someone don’t want to do mocking. Besides, if you have to do a lot of mocking your design is usually too coupled, and should be changed.

So how can you speed up your tests in Maven?

Spring will cache the context and make sure it is only loaded once. But due to details I have not studied; in Maven, this only works for the tests that are in the same suite. It should work with fork=once on the surefire-plugin, but for some reason we need the suites too. Test which one works for you.

Drawbacks? If there is state in your context (database, stateful services) you will have created a dependency between your tests. That’s why, when it comes to database-testing you should use the AbstractTransactionalSpringContextTests which will automatically roll back your changes at the end of your tests. If state is modified in a way that has nothing to do with your database use setDirty() to signal to Spring that the context should be recreated for the next test. Of course then you get the time penalty of recreation.

Categories
Development

TestNG and Junit 4 compared

IBM DeveloperWorks has a good article comparing TestNG and Junit 4. I havn’t really paid much attention to the issue before, but it seems I should have a look at TestNG. It will probably be a better match for our higher-level integrationtests like the author states.