Matchsticks3

© Image by Margarethe Pfründer-Sonn↗

In my former post, I’ve written about the Given - When - Then pattern for unit tests.

In this post, we will have a closer look into the given part:

The given part’s job is making the test pass.

Sometimes there are no preconditions for the test, then the given part is empty. This is a good thing! Keep the given part of the test as small as possible.

business rules’ preconditions vs. preconditions caused by accidental technical complexity

Sometimes there are some technical preparations necessary for the test to pass. These preparations are no preconditions from a business rule’s point of view. They are just accidental complexity caused by the way we implemented things.

I usually put those preparations into a setup section or maybe extract them into a different method.

Why?

For a later reader it is relevant to understand:

Setup: Do anything necessary to make the test execution reach at least the first line of the then part. You may change any details as long as the test still passes.

Given: This is part of the specification. When changing things here, you essentially change the business rule.

So the pattern is now: Setup - Given - When - Then.

Typical mistakes

mixing stubbing and verification

Most people use a mocking framework for faking dependencies, for example Mockito↗.

Sometimes I see people introducing implicit verifications by stubbing like this:

      given(priceCalculator.vatProvider.getVat(NON_FOOD, GERMANY, date)).willReturn(0.19);

What if the implementation then calls?:

      vatProvider.getVat(NON_FOOD, FRANCE, date)

You will get a NullPointerException. That is not what you want!

You want some error message like: “You should have sent GERMANY, but you have sent FRANCE.”

So for most cases: In the given part, use the any() matcher for stubbing; in the then part, check for the expected values:

      // given
      given(priceCalculator.vatProvider.getVat(any(), any(), any())).willReturn(0.19);
      
      // when
      Money actual = priceCalculator.calcPrice(product);
      
      // then
      verify(priceCalculator.vatProvider).getVat(eq(NON_FOOD), eq(GERMANY), eq(date));
      

This gives you both:

  • Better error messages, when the test fails.
  • Explicit documentation of what is expected and what is provided.

btw: I prefer using BDDMockito, because it helps me not confuse the when part vs. Mockito.when()

Too much usage of random values

I really love easy random↗ to create random values for my tests. That has some advantages:

  • You can document that the value of a variable is not relevant.
  • You can have some minimal checks that your code does not only work with specific values.

But sometimes I see things like:

      // given
      Product product = new EasyRandom().nextObject(Product.class);
      
      // when
      Money actual = priceCalculator.calcPrice(product);
      
      // then
      assert(actual).isNotNull();

It might be an extreme example, but sometimes reality is close to this. This test will even increase your line coverage metrics, but it does not really increase confidence in the correctness of the code.

Please: The first tests should use some example values that are a good representation of the real life values.

You can still add some randomization later.

Large setup/given part.

Sometimes the setup and given parts grow and grow. Soon you have 30 lines of code for assembling sample data and doing all the stubbing. You will find that the setup and given parts contain different levels of abstraction↗.

In the end you can’t see the forest for the trees.

To avoid large setup/given parts you can:

  • Extract the setup part into a different helper method. It is not necessary to understand the business rule.
  • In the given part: Use factory methods to assemble test data objects. Pass the relevant values as parameters. Example:
        // given
        Product product = createProduct(NON_FOOD, GERMANY, euros20);

Important business rules hidden in helper methods

The intention of the business rule should be readable without diving into helper methods.

So look at this test:

      // given
      Product product = createTestProduct();
      
      // when
      Money actual = priceCalculator.calcPrice(product);
      
      // then
      assert(actual.getPrice()).isEqualTo(Money.of(20, "EUR"));

So you ask yourself: Why 20? There must be some special values in the given product. So you need to dive into the factory method to see that the NET price was 16.81.

So better you write:

      // given
      Product product = createTestProduct(NON_FOOD, GERMANY, 16.81);

Then you can see all the relevant things necessary for understanding the test method.

My next post is about the then part.

Any comments or suggestions? Leave an issue or a pull request!