Test your code

Fluent assertions in Junit

Yes. Still Junit. We’re not done with it yet. This is about how to make assertions using AssertJ. It’s a cool library which is really easy to use and helps our tests look fluent and nicer. I’m not gonna go over all assertions that are available, I will just focus on some of them that bring something new. We will use java 8 so we can take advantage of lambdas.

What better way to explain things than directly on examples. Since I’m a little bit lazy I will use the same data model described in a previous post. Again I know.  Let’s get to it.

First of all, let’s rewrite the tests using AssertJ.

@Test
public void testSellItemAtAuction() {
    int bid1 = 100;
    int bid2 = 200;
    int bid3 = 300;
    Auction auction = new Auction();
    auction.addItems(buildItems());
    Item goldPen = auction.getItems().get(0);
    auction.sendBidForItem(goldPen, bid1);
    auction.sendBidForItem(goldPen, bid2);
    auction.sendBidForItem(goldPen, bid3);
    Assertions.assertThat(auction.getAllBidsForItem(goldPen))
              .as("There are 3 bids for the gold pen").hasSize(3);
    Assertions.assertThat(auction.getAllBidsForItem(goldPen))
              .as("The bids are " + bid1 + "," + bid2 + "," + bid3)
              .extracting("amount").contains(bid1, bid2, bid3);
    Bid winningBid = auction.sellItem(goldPen);
    Assertions.assertThat(winningBid.getAmount()).as("The winning bid is " + bid3)
               .isEqualTo(bid3);
}

@Test
public void testCannotSellItemAtAuction3() {
    int bid1 = 1000;
    int bid2 = 800;
    Auction auction = new Auction();
    auction.addItems(buildItems());
    Item goldWatch = auction.getItems().get(1);
    auction.sendBidForItem(goldWatch, bid1);
    auction.sendBidForItem(goldWatch, bid2);
    Assertions.assertThat(auction.getAllBidsForItem(goldWatch))
              .as("There are 2 bids for the gold watch")
              .hasSize(2);
    Assertions.assertThat(auction.getAllBidsForItem(goldWatch))
              .as("The bids are " + bid1 + "," + bid2)
              .extracting("amount").contains(bid1, bid2);

    assertThatExceptionOfType(UnsupportedOperationException.class)
            .isThrownBy(() -> auction.sellItem(goldWatch))
            .withMessage("Cannot sell item " + goldWatch.getCode() +
                         " below the reserve price");
}

See how assertj make use of lamdbas?  The classical way(before 8) of doing this would be iterating the list and make assertions at each step. Or extracting elements and make one assertion at the end. In java 8 we would have use a stream and then apply map function on it.

Assertions.assertThat(auction.getAllBidsForItem(goldPen))
          .as("The bids are " + bid1 + "," + bid2 + "," + bid3)
          .extracting("amount")
          .contains(bid1, bid2, bid3);

If you need to check more attributes of an object you can just use org.assertj.core.api.Assertions.tuple. For this particular example it would look something like:

Assertions.assertThat(auction.getAllBidsForItem(goldPen))
          .as("The bids are " + bid1 + "," + bid2 + "," + bid3)
          .extracting("amount","item.reservePrice")
          .contains(tuple(bid1,100), tuple(bid2,100), tuple(bid3,100));

Cool. Let’s check the other updates.

assertThatExceptionOfType(UnsupportedOperationException.class)
        .isThrownBy(() -> auction.sellItem(goldWatch))
        .withMessage("Cannot sell item " + goldWatch.getCode() + " below the reserve price");

Another elegant way of handling exceptions. Check my other post for handling exceptions with rules.

As we know when a test method is failing, the other methods that need to run after it from that test class are not being run. So you don’t know if there is another problem in that test class unless you fix the failing method and run the tests again. This is annoying and consumes time. Well not anymore. AssertJ introduces soft assertions. It collects all the assertion errors instead of stopping at the first one and will present all of them at the end of that test class run.

SoftAssertions softly = new SoftAssertions();
softly.assertThat(auction.getAllBidsForItem(goldPen))
      .as("There are 3 bids for the gold pen")
      .hasSize(2);
softly.assertThat(auction.getAllBidsForItem(goldPen))
      .as("The bids are " + bid1 + "," + bid2 + "," + bid3)
      .extracting("amount")
      .contains(bid1 + 1, bid2 + 1, bid3 + 1);
Bid winningBid = auction.sellItem(goldPen);
softly.assertThat(winningBid.getAmount())
      .as("The winning bid is " + bid3)
      .isEqualTo(bid3 + 1);
softly.assertAll();

The following message will appear:

screen-shot-2016-10-07-at-19-41-43

There is also a @Rule for collecting the assertions globally

@Rule public final JUnitSoftAssertions softly = new JUnitSoftAssertions();

but for some reason on my code this did not work properly, maybe I need to fill a bug. Also this works only for Junit.

A cool script offered by AssertJ consists in transforming your Junit assertions into AssertJ assertions. All you have to do is to run the appropriate script for your machine. The script output is something like this:

screen-shot-2016-10-11-at-21-46-02

Behind it are sed commands and regular expressions.

If you really want to achieve 100% coverage there is a tool that can help you by writing some tests for you. I was exposed to a project where suddenly the client wanted to increase the coverage, it was almost 0. Yes it was legacy. This would have been a nice solution for it. AssertJ built an extensible Assertion Generator that will create a bunch of tests for you. It’s extensible because you can create your own templates for assertions.

Another useful thing is the Condition. As the name implies you can create custom conditions. All  you need to do is to create a new Condition and override matches() method.

Custom assertion have been introduced to let you build assertions that can reflect your application model. There are straightforward to write and can help you greatly in your tests.

SOURCE CODE

1 thought on “Fluent assertions in Junit”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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