upgrading to junit 5 parameterized tests for the first time

I’ve been playing with JUnit 5 for a while. Migrating existing project is something else so I decided to try with my small project cert objectives checker. This project does run on Jenkins so is a good test because it isn’t just on my machine. First I set up Eclipse and Maven to work with JUnit 5.

Switch to Jupiter APIs

Since I only had 5 classes, I updated by hand. For each class:

  1. Changed imports to
    import static org.junit.jupiter.api.Assertions.*;
    import static org.hamcrest.MatcherAssert.assertThat;
    import org.junit.jupiter.api.*;

    (The hamcrest one is so I can keep using assertThat)

  2. Changed assertEquals/assertTrue/etc calls to have String message as last parameter instead of first.
  3. Changed @Before to @BeforeEach
  4. Got distracted and updated tests to Java 8 syntax (nothing to do with JUnit)
  5. Changed to use new ParameterizedTest syntax. More on this…

Parameterized tests

Interestingly Eclipse knows about the new Parameterized Tests dependency natively. But if you use Maven, you need to add a dependency:

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-params</artifactId>
  <version>5.0.0-RC2</version>
</dependency>

Now let’s compare the before and after. I’ve omitted parts that are the same in both (aka the detailed logic of my test). Not that it is secret of anything. (After all the project is public on github.) But it makes it easier to compare the differences with less code.

JUnit 4 version

import static org.junit.Assert.*;

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;

import javax.xml.parsers.*;

import org.junit.*;
import org.junit.runner.*;
import org.junit.runners.*;
import org.junit.runners.Parameterized.Parameters;
import org.w3c.dom.*;

@RunWith(Parameterized.class)
public class CheckForChangesIT {

  @Parameters
  public static List<CertsToCheckEnum[]> suite() {
    List<CertsToCheckEnum[]> result = new ArrayList<>();
    for (CertsToCheckEnum element : CertsToCheckEnum.values()) {
       result.add(new CertsToCheckEnum[] { element });
    }
    return result;
  }
  // ----------------------------------------------------
  private CertsToCheckEnum certToCheck;
  private InputStream stream;
  private Document doc;

  public CheckForChangesIT(CertsToCheckEnum certToCheck) {
    this.certToCheck = certToCheck;
  }
  // ----------------------------------------------------
  @Before
  public void connect() throws Exception {
    String url = XML_URL + certToCheck.getExamNumber();
    stream = new URL(url).openStream();
  }
  @After
  public void close() {
    if (stream != null) {
      try {
        stream.close();
      } catch (Exception e) {
        // ignore
      }
    }
  }
  // ----------------------------------------------------
  @Test
  public void upToDate() throws Exception {
    parseDocument();
    String currentData = convertToString();
    assertSameAsExisting(currentData);
  }

JUnit 5 version

import static org.junit.jupiter.api.Assertions.*;

import java.io.*;
import java.net.*;
import java.nio.file.*;

import javax.xml.parsers.*;

import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import org.w3c.dom.*;

public class CheckForChangesIT {

  private CertsToCheckEnum certToCheck;
  private Document doc;
  // ----------------------------------------------------
  @ParameterizedTest
  @EnumSource(CertsToCheckEnum.class)
  public void upToDate(CertsToCheckEnum c) throws Exception {
    certToCheck = c;
    String url = XML_URL + certToCheck.getExamNumber();
    try (InputStream stream = new URL(url).openStream()) {
      parseDocument(stream);
      String currentData = convertToString();
      assertSameAsExisting(currentData);
    }
  }

Key differences between JUnit 4 and 5 Parameterized Test

  • The JUnit 5 annotation has a different name and is placed on the test rather than the class. This is good because it lets you have some parameterized tests in a class and some regular tests. With JUnit 4, I had to arbitrarily create another class for the non-parameterized parts.
  • JUnit 5 has a number of annotations for building the parameter list without having to write custom code.
  • JUnit 5 injects the parameter as a method parameter rather than an instance variable.
    • In many ways this is nice. For example, I am now using a try-with-resources to close the stream rather than creating in @BeforeEach and having ugly logic to clean it up later. I could have done this in JUnit 4 but it seemed less natural.
    • In the case of my “legacy” test, I use the previously injected instance variable all over the class.  Hence my hack of storing the local variable in the original instance variable as the first line of my test method.
  • The JUnit 5 version is a good bit shorter

One problem

I encountered a minor Eclipse bug with parameterized tests. So I reported it.

Leave a Reply

Your email address will not be published. Required fields are marked *