Using Hamcrest Matchers with Vert.x Unit

Vert.x Unit is a very el­e­gant li­brary to test asyn­chro­nous ap­pli­ca­tions de­vel­oped with vert.x. How­ever be­cause of this asyn­chro­nous as­pect, re­port­ing test fail­ures is not nat­ural for JUnit users. This is be­cause, the failed as­ser­tions need to be re­ported to the test con­text, con­trol­ling the ex­e­cu­tion (and so the out­come) of the test. In other words, in a Vert.x Unit test you can­not use the reg­u­lar Junit as­ser­tions and as­ser­tion li­braries. In this blog post, we pro­pose a way to let you using Ham­crest match­ers in Vert.x Unit tests.

Using Vert.x Unit

Vert.x Unit is a test li­brary made to en­sure the be­hav­ior of vert.x ap­pli­ca­tions. It lets you im­ple­ment tests check­ing asyn­chro­nous be­hav­ior.

Vert.x Unit can be used with Junit. For this, you just need to add the fol­low­ing de­pen­dency to your project:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-unit</artifactId>
  <version>3.2.0</version>
  <scope>test</scope>
</dependency>

If you are using Gra­dle, the de­pen­dency is:

testCompile ‘io.vertx:vertx-unit:3.2.0

If you are using an IDE, just add the vertx-​unit jar to your project class­path.

Ob­vi­ously, you would need to add JUnit too.

No­tice that vertx-​unit does not need JUnit, and can be used with­out it. Check the Vert.x Unit doc­u­men­ta­tion for more de­tails.

Vert.x Unit example

Let’s con­sider this very sim­ple Verticle:

public class MyFirstVerticle extends AbstractVerticle {

  @Override
  public void start(final Future future) throws Exception {
    vertx.createHttpServer()
        .requestHandler(req -> req.response().end("hello vert.x"))
        .listen(8080, done -> {
          if (done.failed()) {
            future.fail(done.cause());
          } else {
            future.complete();
          }
        });
  }
}

It just cre­ates a new HTTP server and when launched it no­ti­fies the future of the com­ple­tion.

To test this ver­ti­cle with Vert.x Unit you would write some­thing like:

@RunWith(VertxUnitRunner.class)
public class MyFirstVerticleTest {

  private Vertx vertx;

  @Before
  public void setUp(TestContext context) {
    vertx = Vertx.vertx();
    vertx.deployVerticle(MyFirstVerticle.class.getName(),
      context.asyncAssertSuccess());
  }

  @Test
  public void test(TestContext context) {
    Async async = context.async();
    vertx.createHttpClient().get(8080, "localhost", "/")
      .handler(response -> {
        context.assertEquals(200, response.statusCode());
        response.bodyHandler(buffer -> {
          context.assertEquals("hello vert.x", buffer.toString("utf-8"));
          async.complete();
        });
      })
      .end();
  }
}

First, the test class is an­no­tated with @RunWith(VertxUnitRunner.class), in­struct­ing JUnit to use this spe­cial run­ner. This run­ner lets you in­ject a TestContext pa­ra­me­ter into every test meth­ods (as well as @Before and @After) to han­dle the asyn­chro­nous as­pect of the test.

In the setUp method, it cre­ates a new in­stance of Vertx and de­ploy the ver­ti­cle. Thanks to context.asyncAssertSuccess(), it waits until the suc­cess­ful com­ple­tion of the ver­ti­cle de­ploy­ment. In­deed, the de­ploy­ment is asyn­chro­nous, and we must be sure that the ver­ti­cle has been de­ployed and has com­pleted its ini­tial­iza­tion be­fore start­ing to test it.

The test() method cre­ates an Async ob­ject that will be used to re­port when the test has been com­pleted. Then it cre­ates an HTTP client to emit a re­quest on the server from our ver­ti­cle and check that:

  1. the HTTP code is 200 (OK)
  2. the body is hello vert.x

As you can see, to im­ple­ment the checks, the as­ser­tions method are called on the TestContext ob­ject, which con­trol the test ex­e­cu­tion. When every­thing has been tested, we call async.complete() to end the test. If an as­ser­tion failed, the test is ob­vi­ously stopped. This would not be the case if you would use reg­u­lar Junit as­ser­tions.

Using the Hamcrest Matchers

In the pre­vi­ous ex­am­ple, we used the the as­ser­tions avail­able from the TestContext in­stance. How­ever it pro­vides a lim­ited set of meth­ods. Ham­crest is a li­brary of match­ers, which can be com­bined in to cre­ate flex­i­ble ex­pres­sions of in­tent in tests. It is very con­ve­nient when test­ing com­plex ap­pli­ca­tions.

Ham­crest can­not be used di­rectly as it would not re­port the fail­ure on the TestContext. For this pur­pose we cre­ate a VertxMatcherAssert class:

public class VertxMatcherAssert {

  public static <T> void assertThat(TestContext context, T actual,
    Matcher<? super T> matcher) {
    assertThat(context, "", actual, matcher);
  }

  public static <T> void assertThat(TestContext context, String reason,
    T actual, Matcher<? super T> matcher) {
    if (!matcher.matches(actual)) {
      Description description = new StringDescription();
      description.appendText(reason)
          .appendText("\nExpected: ")
          .appendDescriptionOf(matcher)
          .appendText("\n     but: ");
      matcher.describeMismatch(actual, description);
      context.fail(description.toString());
    }
  }

  public static void assertThat(TestContext context, String reason,
    boolean assertion) {
    if (!assertion) {
      context.fail(reason);
    }
  }
}

This class pro­vides assertThat method that re­ports error on the given TestContext. The com­plete code is avail­able here.

With this class, we can re-​implement our test as fol­lows:

@Test
public void testWithHamcrest(TestContext context) {
  Async async = context.async();
  vertx.createHttpClient().get(8080, "localhost", "/").handler(response -> {
    assertThat(context, response.statusCode(), is(200));
    response.bodyHandler(buffer -> {
      assertThat(context, buffer.toString("utf-8"), is("hello vert.x"));
      async.complete();
    });
  }).end();
}

To ease the usage, I’ve added two im­port sta­tic:

import static io.vertx.unit.example.VertxMatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

You can use any Ham­crest matcher, or even im­ple­ment your own as soon as you use the assertThat method pro­vided by VertxMatcherAssert.

Conclusion

In this post we have seen how you can com­bine Ham­crest and Vert.x Unit. So, you are not lim­ited any­more by the set of as­sert meth­ods pro­vided by Vert.x Unit, and can use the whole ex­pres­sive­ness of Ham­crest Match­ers.

Don’t for­get that you still can’t use the assert meth­ods from Junit, as they don’t re­port on the TestContext.

Next post

Intro to Vert.x Shell

Vert.x Shell provides an extensible command line for Vert.x, accessible via SSH, Telnet or a nice Web interface. Vert.x Shell comes out of the box with plenty of commands.

Read more
Previous post

Real-time bidding with Websockets and Vert.x

The expectations of users for interactivity with web applications have changed over the past few years. Users during bidding in auction no longer want to press the refresh button.

Read more
Related posts

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more

My first Vert.x 3 Application

Let's say, you heard someone saying that Vert.x is awesome. Ok great, but you may want to try it by yourself. Well, the next natural question is “where do I start ?”

Read more

Some Rest with Vert.x

This post is part of the Introduction to Vert.x series. Let’s go a bit further this time and develop a CRUD-ish application

Read more