How to Parameterize beforeEach() in JUnit 5?
Asked Answered
W

3

25

I am using JUnit 5 as my Test Runner.

In the setup method, I have hardcoded 3 params (platformName, platformVersion, and deviceName). I have a test method that should test on various combinations... This means, when running my testLogin() test, it should run on multiple platform names, versions, device names...

So, I tried as below...

@BeforeEach
@CsvSource({"IOS,13.0,iPhone X Simulator", "IOS,13.2,iPhone Simulator", "IOS,13.3,iPhone XS Simulator"})
void setUp(String platformName, String platformVersion, String deviceName) throws MalformedURLException {
    ....
    capabilities.setCapability("platformName", platformName);
    capabilities.setCapability("platformVersion", platformVersion);
    capabilities.setCapability("deviceName", deviceName);
    capabilities.setCapability("methodName", testInfo.getDisplayName());
}

My question is, how can beforeEach() method can be parameterized? Also, I want to get the test method name... So, if I specify the parameters, then where should I specify TestInfo param.

Please help me. I have also seen the below question...

Parameterized beforeEach/beforeAll in JUnit 5

========

public class TestBase {

    @BeforeEach
    void setUp(TestInfo testInfo) throws MalformedURLException {
        MutableCapabilities capabilities = new MutableCapabilities();
        capabilities.setCapability("platformName", "iOS");
        capabilities.setCapability("platformVersion", "13.2");
        capabilities.setCapability("deviceName", "iPhone Simulator");
        capabilities.setCapability("name", testInfo.getDisplayName());
        capabilities.setCapability("app", “/home/my-user/testapp.zip");

        driver = new IOSDriver(
                new URL("https://192.168.1.4:5566/wd/hub"),
                capabilities
        );
    }
}
public class LoginTest extends TestBase {

    @Test
    public void testLogin() {
        driver.findElement(By.id("user-name")).sendKeys(“myuser);
        driver.findElement(By.id("password")).sendKeys(“mypassword);
        driver.findElement(By.id(“login_btn”)).click();
        assertTrue(true);
    }
}
Whencesoever answered 27/5, 2020 at 6:27 Comment(0)
I
7

You can't parameterize @BeforEach method. JUnit5 supports only parameterized tests (test methods).

Parameterized tests are declared just like regular @Test methods but use the @ParameterizedTest annotation instead. In addition, you must declare at least one source (e.g. @CsvSource, @ValueSource, etc.)

For example:

    @ParameterizedTest
    @CsvSource({
        "apple,         1",
        "banana,        2",
        "'lemon, lime', 0xF1"
    })
    void testWithCsvSource(String fruit, int rank) {
        assertNotNull(fruit);
        assertNotEquals(0, rank);
    }
Iand answered 30/5, 2020 at 19:59 Comment(1)
The javadocs specify @BeforeEach methods must have a void return type, must not be private, and must not be static. They may optionally declare parameters to be resolved by ParameterResolvers.Prairie
G
8

Let's try this awesome solution of @lamektomasz.

  • Create file CustomParameterResolver.java to resolve parameters for @BeforeEach and @AfterEach annotation.
package com.example;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter;
import org.junit.jupiter.engine.extension.ExtensionRegistry;

public class CustomParameterResolver implements BeforeEachMethodAdapter, ParameterResolver {

  private ParameterResolver parameterisedTestParameterResolver = null;

  @Override
  public void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry)
      throws Throwable {
    Optional<ParameterResolver> resolverOptional = registry.getExtensions(ParameterResolver.class)
        .stream()
        .filter(parameterResolver ->
            parameterResolver.getClass().getName()
                .contains("ParameterizedTestParameterResolver")
        )
        .findFirst();
    if (!resolverOptional.isPresent()) {
      throw new IllegalStateException(
          "ParameterizedTestParameterResolver missed in the registry. Probably it's not a Parameterized Test");
    } else {
      parameterisedTestParameterResolver = resolverOptional.get();
    }
  }

  @Override
  public boolean supportsParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) throws ParameterResolutionException {
    if (isExecutedOnAfterOrBeforeMethod(parameterContext)) {
      ParameterContext pContext = getMappedContext(parameterContext, extensionContext);
      return parameterisedTestParameterResolver.supportsParameter(pContext, extensionContext);
    }
    return false;
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext,
      ExtensionContext extensionContext) throws ParameterResolutionException {
    return parameterisedTestParameterResolver.resolveParameter(
        getMappedContext(parameterContext, extensionContext), extensionContext);
  }

  private MappedParameterContext getMappedContext(ParameterContext parameterContext,
      ExtensionContext extensionContext) {
    return new MappedParameterContext(
        parameterContext.getIndex(),
        extensionContext.getRequiredTestMethod().getParameters()[parameterContext.getIndex()],
        Optional.of(parameterContext.getTarget()));
  }

  private boolean isExecutedOnAfterOrBeforeMethod(ParameterContext parameterContext) {
    return Arrays.stream(parameterContext.getDeclaringExecutable().getDeclaredAnnotations())
        .anyMatch(this::isAfterEachOrBeforeEachAnnotation);
  }

  private boolean isAfterEachOrBeforeEachAnnotation(Annotation annotation) {
    return annotation.annotationType() == BeforeEach.class
        || annotation.annotationType() == AfterEach.class;
  }
}
  • Create MappedParameterContext.java file
package com.example;

import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.platform.commons.util.AnnotationUtils;

public class MappedParameterContext implements ParameterContext {

  private final int index;
  private final Parameter parameter;
  private final Optional<Object> target;

  public MappedParameterContext(int index, Parameter parameter,
      Optional<Object> target) {
    this.index = index;
    this.parameter = parameter;
    this.target = target;
  }

  @Override
  public boolean isAnnotated(Class<? extends Annotation> annotationType) {
    return AnnotationUtils.isAnnotated(parameter, annotationType);
  }

  @Override
  public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType) {
    return Optional.empty();
  }

  @Override
  public <A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType) {
    return null;
  }

  @Override
  public int getIndex() {
    return index;
  }

  @Override
  public Parameter getParameter() {
    return parameter;
  }

  @Override
  public Optional<Object> getTarget() {
    return target;
  }
}
  • Add resolver to your test project
package com.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@ExtendWith(CustomParameterResolver.class)
public class BaseTest {

  @BeforeEach
  public void beforeEach(String platformName, String platformVersion, String deviceName) {
    System.out.println("Before each:");
    System.out.println("platformName: " + platformName);
    System.out.println("platformVersion: " + platformVersion);
    System.out.println("deviceName: " + deviceName);
  }

  @ParameterizedTest
  @CsvSource({"IOS,13.0,iPhone X Simulator", "IOS,13.2,iPhone Simulator", "IOS,13.3,iPhone XS Simulator"})
  void testLogin(String platformName, String platformVersion, String deviceName) {
    // ...
    capabilities.setCapability("platformName", platformName);
    capabilities.setCapability("platformVersion", platformVersion);
    capabilities.setCapability("deviceName", deviceName);
    capabilities.setCapability("methodName", testInfo.getDisplayName());
  }
}

  • Output:
Before each:
platformName: IOS
platformVersion: 13.0
deviceName: iPhone X Simulator
===========
Before each:
platformName: IOS
platformVersion: 13.2
deviceName: iPhone Simulator
===========
Before each:
platformName: IOS
platformVersion: 13.3
deviceName: iPhone XS Simulator
===========
Gildagildas answered 21/9, 2021 at 8:43 Comment(3)
The problem with this approach is if one applies @ExtendWith(CustomParameterResolver.class), then he 'must' use @ParameterizedTest. This might seem obvious but imagine: I have a base classe for testing where I'd extend it with @ExtendWith(CustomParameterResolver.class), only to convert 'a few' derived test classes to parameterized testing. This will not be possible. I now must conver ALL derived classes to use parameterized testing. Am I right? Any solution/adjustments? (I am new to java)Comprehensive
@Comprehensive If not all of your tests want parameterization, then putting parameterization into @BeforeEach couldn't possibly be correct. Appropriately parameterize only the tests that need it (as per jumb0jet's answer).Rockies
It works, but what if my BeforeEach method already uses TestInfo placeholder?Orris
I
7

You can't parameterize @BeforEach method. JUnit5 supports only parameterized tests (test methods).

Parameterized tests are declared just like regular @Test methods but use the @ParameterizedTest annotation instead. In addition, you must declare at least one source (e.g. @CsvSource, @ValueSource, etc.)

For example:

    @ParameterizedTest
    @CsvSource({
        "apple,         1",
        "banana,        2",
        "'lemon, lime', 0xF1"
    })
    void testWithCsvSource(String fruit, int rank) {
        assertNotNull(fruit);
        assertNotEquals(0, rank);
    }
Iand answered 30/5, 2020 at 19:59 Comment(1)
The javadocs specify @BeforeEach methods must have a void return type, must not be private, and must not be static. They may optionally declare parameters to be resolved by ParameterResolvers.Prairie
L
2

AFAIK, there is no way to parameterize @BeforeEach in Junit5.

This could be achieved by a new class-level parameterization feature which seems to be a very requested feature but when I write this lines it is not yet available. See how it could looks like in the future.

Waiting I know 3 workaround :

  1. using custom Parameter Resolver Extension,
  2. using dynamic with @TestFactory,
  3. using abstract + concrete class.

See, more details about this 3 ways at : https://github.com/junit-team/junit5/issues/3157#issuecomment-1435009129

Lenzi answered 20/2, 2023 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.