Assert object pattern

One of the most significant problems with tests written by developers is the fact that such tests do not verify business invariants but the underlying technical details.

When a developer writes a shopping cart part of the application and implements adding an item to the reservation, the test most likely looks like this:

1
2
3
4
5
6
7
8
val reservation = Reservation(...)
val item = Item(...)

reservation.add(item)

reservation.items has size 1
reservation.items.head shouldEqual item
reservation.totalPrice shouldEqual item.price * 1.23

The developer wants to know whether the shopping cart contains the added item, there are no other items in the shopping cart, and whether the VAT tax has been added to the total price of the reservation.

What is wrong with that? This code dissects the objects. I look at the code, and all I see are technical details. Where is the meaning?

Step 1. Hide the assertion

How can we write such tests better? We can use the Assert Object pattern. It is an object that wraps the result and exposes methods which verify the object’s properties. The most trivial usage of this pattern would look like this:

1
2
3
4
5
6
7
8
9
val reservation = Reservation(...)
val item = Item(...)

reservation.add(item)

val assertion = Assert(reservation)
assertion.hasSize(1)
assertion.hasItem(item)
assertion.hasPrice(item.price * 1.23)

It hides the dissecting of the result, but it does not look much better than the previous example.

Step 2. Show the meaning

Fortunately, we can fix that. If we invest some time in implementing the assert objects, we can create a nice fluent interface:

1
2
3
4
5
6
7
8
9
val reservation = Reservation(...)
val item = Item(...)

reservation.add(item)

Assert(reservation)
 .hasItem(item)
 .hasNoOtherItems()
 .hasItemPriceWithVATTax(item)

Why is it better? This is the text we had in the Jira ticket. The text that was written in the specification is written in code. The test shows the user’s intent.

Why don’t we do it?
We must create such assert objects ourselves. We must write the methods of the fluent interface. Assert objects are not given to us for free. It is much easier to import assertions from a testing library and verify the technical details.

Did you enjoy reading this article?
Would you like to learn more about software craft in data engineering and MLOps?

Subscribe to the newsletter or add this blog to your RSS reader (does anyone still use them?) to get a notification when I publish a new essay!

Newsletter

Do you enjoy reading my articles?
Subscribe to the newsletter if you don't want to miss the new content, business offers, and free training materials.

Bartosz Mikulski

Bartosz Mikulski

  • Data/MLOps engineer by day
  • DevRel/copywriter by night
  • Python and data engineering trainer
  • Conference speaker
  • Contributed a chapter to the book "97 Things Every Data Engineer Should Know"
  • Twitter: @mikulskibartosz
Newsletter

Do you enjoy reading my articles?
Subscribe to the newsletter if you don't want to miss the new content, business offers, and free training materials.