Parameterisation with JUnit5

Parameterisation with JUnit5

The background

This was never intended to be an article in itself, but one of the problems with working on this late at night, is that sometimes I have to finish up, even though I know that there is another simple change I would like to make. In this case, my DriverFactoryTests were just screaming out for parameterisation. I know I had added some Windows 10 checks in there, but as I was of course running the tests over a grid, that was unneccessary and everything else was just the same for every browser option. Time to tidy things up and get to grips with JUnit5s version of parameterisation.

JUnit4 Parameterisation

JUnit4 paramterisation is simple enough but it has some major drawbacks. Most obviously, you can only write a single parameterised test method per class. (Or to be precise you can only have a single set of parameters per class, but that is effectively the same!) You could not mix a combination of parameterised and standard test methods in a single class.

JUnit5 Parameterisation example

To demonstrate some of the options in JUnit5 lets just dive straight in. Firstly we need to pull in the required dependency, so add in build.gradle:

testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.0.1'
testCompile group: 'org.apiguardian', name: 'apiguardian-api', version: '1.0.0'

Notes,

  • as JUnit 5 is now up to v5.0.1 we can update the other dependencies too.
  • The second of these is to fix a compilation warning due to a change in v5.0.1, there seems to be some discussion as to whether this will be permanent.

Then in DriverFactoryTests to indicate that a METHOD is using parameters:

import org.junit.jupiter.params.ParameterizedTest;
..

@ParameterizedTest(name = "{2}")

Note, the {2} indocating that the third parameter shold be used for the tests DisplayName! I can then pull all the test code into a single parameterised method requiring 2 parameters:

void driverFactoryWorks(final DriverType driverType, final String browserName) {
    setDriverManager(DriverManagerFactory.getManager(driverType));
    getDriverManager().startService();
    assertThat(getDriverManager().createDriver())
            .as(String.format("Checking That browser is of type %s", browserName))
            .isEqualToIgnoringCase(browserName);
    final String homePageURL = "https://www.bbc.com/";
    assertThat(getDriver(homePageURL).getCurrentUrl())
            .as(String.format("Check that browser '%1$s' successfully loads the homepage '%2$s'", browserName, homePageURL))
            .isEqualToIgnoringCase(homePageURL);
}

Then we just need a way to pass in the three parameters (two for the code and one for the DisplayName.)

JUnit5 offers a number of options here. I’m going to look at just a few here, but if you want more details use the JUnit5 Documentation and this post at reinhard.codes covers the options well. CSV files are a traditional source of parameterised test data, and they can be used by annotating the method with the @CsvFileSource annotation e.g.:

@CsvFileSource(resources = "/two-column.csv")

Much easier than dealing with files however is that we can define a simple CSV inline using the @CsvSource annotation e.g.:

 @CsvSource({
 "CHROME_LOCAL, Chrome, Chrome Browser Tests can be run on screen locally",
 "CHROME_LOCAL_HEADLESS, Chrome, Chrome Browser Tests can be run headless locally."
 })

JUnit5 has a range of type converters to generate the required parameters from the strings. If you have more complex objects to generate, you can alternatively define a static method to generate your parameters using the @MethodSource annotation e.g.:

@MethodSource(“arguments”)

and the method to provide a Stream of objects.

private static Stream<Arguments> arguments() {
    return Stream.of(
            Arguments.of(DriverType.CHROME, "Chrome", "Chrome Browser Tests can be run on the grid"),
            Arguments.of(DriverType.EDGE, "MicrosoftEdge", "Edge Browser Tests can be run on the grid"),
            Arguments.of(DriverType.FIREFOX, "Firefox", "Firefox Browser Tests can be run on on the grid"),
            Arguments.of(DriverType.IE, "Internet Explorer", "Internet Explorer Browser Tests can be run on the grid")
);
}

This leaves us with the much simplified class however showing that we can provide our parameters by more than one method.:

package tech.alexontest.poftutor;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import tech.alexontest.poftutor.infrastructure.AbstractCrossBrowserTest;
import tech.alexontest.poftutor.infrastructure.DriverManagerFactory;
import tech.alexontest.poftutor.infrastructure.DriverType;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

@Tag("Framework")
class DriverFactoryTests extends AbstractCrossBrowserTest{

    @ParameterizedTest(name = "{2}")
    @CsvSource({
            "CHROME_LOCAL, Chrome, Chrome Browser Tests can be run  on screen locally",
            "CHROME_LOCAL_HEADLESS, Chrome, Chrome Browser Tests can be run headless locally."
    }) // Simplest way to pass multiple parameters, places them right above the test.
    @MethodSource("arguments") // Can be used to pass more complex to construct objects.
    void driverFactoryWorks(final DriverType driverType, final String browserName) {
        setDriverManager(DriverManagerFactory.getManager(driverType));
        getDriverManager().startService();
        assertThat(getDriverManager().createDriver())
                .as(String.format("Checking That browser is of type %s", browserName))
                .isEqualToIgnoringCase(browserName);
        final String homePageURL = "https://www.bbc.com/";
        assertThat(getDriver(homePageURL).getCurrentUrl())
                .as(String.format("Check that browser '%1$s' successfully loads the homepage '%2$s'", browserName, homePageURL))
                .isEqualToIgnoringCase(homePageURL);
    }

    private static Stream<Arguments> arguments() {
        return Stream.of(
                Arguments.of(DriverType.CHROME, "Chrome", "Chrome Browser Tests can be run on the grid"),
                Arguments.of(DriverType.EDGE, "MicrosoftEdge", "Edge Browser Tests can be run on the grid"),
                Arguments.of(DriverType.FIREFOX, "Firefox", "Firefox Browser Tests can be run on on the grid"),
                Arguments.of(DriverType.IE, "Internet Explorer", "Internet Explorer Browser Tests can be run on the grid")
    );
    }
}

Looking at my other test classes it is clear that the NavigationTests are also an obvious candidate for parameterisation, so for that and we can even add some extra tests:

package tech.alexontest.poftutor;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import tech.alexontest.poftutor.infrastructure.AbstractSingleDriverTest;

import static org.assertj.core.api.Assertions.assertThat;

@Tag("Navigation")
class NavigationTests extends AbstractSingleDriverTest {
    private final String homePageURL = "https://alexanderontesting.com/";

    @ParameterizedTest(name = "{1}")
    @CsvSource({
            "https://alexanderontesting.com/, Homepage URL displays correctly",
            "https://alexanderontesting.com/, http is directed to https",
            "https://alexanderontesting.com/, https://alexanderontesting.com/ directs here",
            "https://alexanderontesting.com/, https://alexanderontesting.com/ directs here",
            "https://alexontest.tech/, https://alexontest.tech is redirected here",
            "http://alexontest.tech/, http://alexontest.tech is redirected here",
            "https://www.alexontest.tech/, https://www.alexontest.tech is redirected here",
            "http://www.alexontest.tech/, http://www.alexontest.tech is redirected here"
    })

    void allUrlsLeadToHere(final String urlToTest) {
        assertThat(getDriver(urlToTest).getCurrentUrl())
                .isEqualToIgnoringCase(homePageURL);
    }
}

Note: in this case I cannot disable individual tests (other than by commenting them out!)

That’s it for this quick one! Not at all what I had in mind for this week.

Progress made:

  • Simple parameterisation of 2 test classes with JUnit5 @ParameterizedTest.

Lessons learnt:

  • Parameterisation is far easier and has less issues with JUnit5 than JUnit4.
  • You can use multiple parameter providers on a single test method.
  • JUnit 5.0.1 has been released but needs an optional dependency on apiguardian-api.

 

 

One thought on “Parameterisation with JUnit5

Comments are closed.

Comments are closed.