Unit testing With BlockHound

Learn to run unit tests using BlockHound.

Slipping BlockHound into the main entry point for our application is an effective warmup, but it’s not the best strategy. That approach leaves BlockHound active in production.

It’s better to leverage BlockHound in our test cases.

Add BlockHound to our application

To add BlockHound’s JUnit support, we add the following to our pom.xml file:

<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>1.0.3.RELEASE</version>
<scope>test</scope>
</dependency>
Adding BlockHound to the pom.xml file

This is required to bring in BlockHound and support for its JUnit Platform, TestExecutionListener, meaning that it registers itself to look for blocking calls inside test methods.

What, exactly, is BlockHound looking for from a test-case perspective? Let’s start with a somewhat contrived example:

class BlockHoundUnitTest {
// tag::obvious-failure[]
@Test
void threadSleepIsABlockingCall() {
Mono.delay(Duration.ofSeconds(1)) // <1>
.flatMap(tick -> {
try {
Thread.sleep(10); // <2>
return Mono.just(true);
} catch (InterruptedException e) {
return Mono.error(e);
}
})
.as(StepVerifier::create)
.verifyErrorMatches(throwable -> {
assertThat(throwable.getMessage())
.contains("Blocking call! java.lang.Thread.sleep");
return true;
});
}
// end::obvious-failure[]
}
BlockHound detection inside a unit test

Here’s a breakdown of the code above:

  1. In line 6, we insert a deliberate delay that forces this whole flow onto a Reactor thread, putting it into BlockHound’s scope.

  2. In line 9, Thread.sleep is a blocking call because it ...