• 10 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 1/4/21

Label your tests with advanced JUnit annotations

When you’re looking for something in a book, you can usually check the contents page and jump to the right chapter. 📖 If you have to read through every page to find what you need, it either has a bad index or is not structured well. The same is true for your tests. Let's check out some ways to make your tests readable.

Use @Category to label similar tests

JUnit 4 comes with some useful annotations which let you label your tests, a little like marking which chapter or topic they fall under.  If someone asked about where to find your tests on say, temperature conversion, you could find all of them quickly by labeling them with the same category name. JUnit4 provides @Category for this very purpose. Every class or method you want to label gets annotated with  @Category.

Check out this ConversionCalculator test with line numbers on the left:

@Category(Categories.ConversionTests.class)
public class ConversionCalculatorTest {
    
        private ConversionCalculator calculatorUnderTest = new ConversionCalculator();
        
        @Test
        @Category({Categories.FahrenheitTests.class, Categories.TemperatureTests.class})
        public void celsiusToFahrenheit_returnsAFahrenheitTemperature_whenCelsiusIsZero() {
            Double actualFahrenheit = calculatorUnderTest.celsiusToFahrenheit(celsiusTemperature: 0.0)
            assertThat(actualFahrenehit, is(equalTo(operand:32.0)));
        }
        
        @Test
        @Category({Categories.TemperatureTests.class})
        public void FahrenheitToCelsius_returnsZerpCelsiusTempurature_whenThirtyTwo() {
            Double actualCelsius = calculatorUnderTest.fahrenheitToCelsius(fahrenheitTemperaure:32.0);
            assertThat(actualCelsius, is (equalTo(operand:0.0)));
        }
        
        @Test
        public void litresToGallons_returnsOneGallon_whenConvertingTheEquivalentLitres() {
            Double actualLitres = calculatorUnderTest.litresToGallons(litreVolume:3.78541);
            assertThat(actualLitres, is(equalTo(operand:1.0)));
        }
        
        @Test
        public void radiusToAreaOfCircle_returnsPi_whenWeHaveARadiusOfOne() {
            Double actualArea = calculatorUnderTest.radiusToAreaOfCircle(1.0);
            assertThat(actualArea, is(equalTo(PI)));
        }
}

Have a look at line 1. All the tests in the ConversionCalculatorTests class have been marked as  conversion tests. Line 7 additionally marks one test as a Fahrenheit test. Lines 7 and 14 mark two tests as also being TemperatureTests.

@Category  has one slightly annoying restriction: The labels you use should be classes or interfaces. So to label tests as Fahrenheit, you first create an interface which contains all your labels, like below:

public interface Categories {
    interface TemperatureTests {}
    interface FahrenheitTests {}
    interface ConversionTests {}
}

 Then mark your class or test with  Categories.FahrenheitTests.

This is how you run just the FahrenheitTests using Maven:

mvn -Dgroups='com.openclassrooms.testing.Categories$FahrenheitTests' test`

And the results look like this:

Results!

Use the @Tag to categorize tests more easily

So far we’re focused on JUnit4 features. JUnit5 has recently been released, and you’re most likely to see both JUnit4 and JUnit5 in the workplace today. As a Java engineer, it will be up to you, and your team, to decide which to use on new projects. A company’s standards can take a while to change, but fortunately, junit-vintage-engine  is a dependency which allows you to migrate your JUnit4 tests to Junit5 more easily.

New versions always bring improvements. Take @Tag  from JUnit5 for instance. Using  @Category to label your tests meant that you had to create an empty interface each time. Well that’s a waste, isn’t it? It only exists to be used in your annotations.   @Tag is way better. You think of a label and use it. @Tag(“fahrenheitTest”) would “just work!™” You also get to describe your tests in plain English, so when they fail, you’re not left staring at a test name like CalculatorTests and wondering what it was about the calculator that you wanted to prove!?

I've highlighted how you can use the @Tag  annotation along with two other great annotations:  @DisplayName  and  @Nested.  Let's look at the most interesting bits step by step: 

  • ( 1 )@Tag  marks all the tests as ConversionTests. 

  • ( 2 )@DisplayName  lets you name your tests so they're readable for anyone. 

  • ( 3 )@Nested  lets you group tests in an inner class. 

P2Ch1 JUnit5

  • ( 4 ) You can add  @Displayname  and  @Tag  to each  @Test  and  @Nested block

  • ( 5 ) Many tests are grouped into  @Nested. This way one failing test would fail the whole group.

  • ( 6 ) Other tests can be separated out (and remain untested).

When that JUnit5 test runs, the results also read more naturally!!

To get started, import the JUnit 5 annotations

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
Try it out for yourself!

Have a look at the code and see if you can TDD a conversion from miles to kilometers. Use the  @Tag  to tag these tests with distance. Also, use the @DisplayName tags to make the test meaningful in the test report.

Clone the repository below and run Maven commands in its README.md.

git clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJavaC.git

Or use your IDE to clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJavaC.git

Use @RunWith to designate a test runner

@RunWith is how you decide which class runs your test for you and makes your annotations work.

Hold on, doesn't JUnit just do this for me?

Spot on! Yes, it does. Do you remember our first JUnit tests?  @Test  was all we needed to mark those methods as tests. JUnit4 and later are designed to just work and start reporting on assertions made in methods marked with  @Test That's all that's needed to make all those other great annotations, like @Before and @After work too! 

But...a special class called BlockJUnit4ClassRunner is used to make your test run and be able to provide reporting information for plugins like Surefire. This type of special class is called a test runner. It's also the reason you can use all those other great annotations we've shown you!

Will JUnit 5's runner know what to do with my JUnit 4 annotations?

Yes. If you work on a project using JUnit 5, you can use its Vintage test engine to support all of JUnit4’s runners. If you're interested in seeing this in practice, check out the JUnit team's examples on GitHub.

So, what can I do with a runner?

Many custom runners exist to help make test writing even easier. All you do is stick an  @RunWith(UsefulRunner.class)  before your test class. Just replace UsefulRunner with one of the runners you want to use.

You can use custom runners such as  Suite.class in JUnit4 to help you use new annotations which better organize or empower your testing. The Suite runner groups a number of test classes so they are associated, and can pass or fail together.

This can be helpful if putting your tests into groups like TemperatureSuite, ArithmeticSuite, etc.

Isn't this similar to  @Category  and  @Tag?

It is! The difference here is that your Suite is a test itself, which reports as having passed or failed.  @Category  and  @Tag  are simply about grouping which tests you run.

Let's see this in action:

Many custom runners exist to help make your test writing even easier. All you do is stick an @RunWith(UsefulRunner.class) before your test class. Just replace UsefulRunner with one of the runners you want to use. Here are some examples:

@RunWith(Suite.class)

@RunWith(Suite.class)
@RunWith(Suite.class)

This lets you group tests into a suite so if one fails, the whole suite fails. Be careful not over complicate your suites!

@RunWith(MockitoJunitRunner.class)

@RunWith(MockitoJunitRunner.class)
@RunWith(MockitoJunitRunner.class)

Once you use this runner, you can use the annotation @Mock. @Mock creates a pretend (or mock) ConversionCalculator which our class under tests needs. A mock allows us to focus on testing Calculator by itself. We then set up how it behaves with when and test how it’s used with verify:

@RunWith(MockitoJunitRunner.class)
@RunWith(MockitoJunitRunner.class)

@SpringJUnitRunner

The popular Spring framework has its own runner which helps us create mocks of classes which Spring would usually be responsible for creating and destroying. You can read about it here!

@Ignore

Have you ever been working hard on a project, but had someone next to you who just talked and talked and talked? And you couldn’t get anything done because you were so distracted? You might have felt bad, but I bet you wished for a mute button.

I’ve had tests which were like that! Ones which failed and gave me lines and lines of red text, which I had no idea what to do with. Obviously, it’s best to fix the test if you can. However, we live in a world where things need to get delivered to keep our businesses afloat. If the test was badly written or you don’t even know what confidence it’s supposed to be giving you, just tell it to shut up! You can do that with JUnit’s @Ignore. 

For example sticking the following annotation before your @Test would stop a test from running and make it clear, why! For example: @Ignore(“Disabled as this fails every Tuesday and it seems to be re-testing unnecessarily”). Make sure you say why you’re ignoring it and promise to come back and fix it later!

Try it out for yourself!

Checkout the JUnit 4 project here. See if you can solve the little challenge in the README!

Let's recap!

  • We can describe what a test is about using JUnit4’s @Category and JUnit5’s @Tag

  • @RunWith can be used to point at a custom JUnit runner which gives us more useful annotations.

  • @Ignore can be used to silence less than helpful tests. 

Example of certificate of achievement
Example of certificate of achievement