javajunit5junit5-extension-model

Is it possible to disable a Parameterized test basis arguments in Junit5


I have a scenario where I'm trying to disable parameterized tests but I don't want to disable all of them. Consider the below mock scenario

@CsvSource({
      "1,1",
      "2,2",
      "3,not3"
  })
  @ExtendWith(DisableParameterized.class)
  @ParameterizedTest( name = "Test for {0} is equal to {1}")
  void equality(String expected, String actual) {
    assertEquals(expected, actual, "Not as expected");
  }

Is it possible to write a DisableParameterized implements ExecutionCondition which can disable one of the Parameterized tests basis some meta data.

The only proper info I can make out about ParameterizedTests in the

public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext extensionContext)

is the displayName Is there some other way to generically provide some sort of meta data to disable the Parameterized tests ?

if I mark them as @Disabled, it disables all of the ParameterizedTests to the point that it doesn't even invoke the individual tests and stops at the method level.

Edit : As mentioned by Sam below, it's not possible to do it using standard API till the issues mentioned are fixed/implemented and release in a newer version. I was however able to write a code using reflection to achieve what I wanted, so I've added an answer below


Solution

  • Currently, till the actual API is implemented under the issues Sam has mentioned, the only way to extract arguments is as below. i.e. using reflection Which is a dirty way for most people, but that's the only way

    Then after extracting the arguments, we can manipulate them however we want.

    @Override
      public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        return disableRequiredOnParameterizedTests(context);
      }
    
    private ConditionEvaluationResult disableRequiredOnParameterizedTests(ExtensionContext context) {
        // See if my custom annotation is present on the test
        var onParameters = findRepeatableAnnotation(context, DisableOnParameter.class);
        // Or if the test is a Parameterized test after all
        // We have access to arguments when the test will be at method level, i.e. MethodExtensionContext
        if (!MethodExtensionContext.class.isAssignableFrom(context.getClass())
            || findAnnotations(context, ParameterizedTest.class).isEmpty()
            || onParameters.isEmpty()) {
          return enabled("Not set to disable");
        }
        List<Object> arguments = getArguments(context);
        boolean enable = true;
        // Do what you want with arguments
        .
        .
        .
        return enable ? enabled("Enabling") ? disabled("Disabling");
      }
    
    /**
       * A blanket exception safe function to extract arguments from Extension context using
       * reflection since we don't have direct access to the API
       * https://github.com/junit-team/junit5/issues/1139
       *
       * @param context needs to be of {@link MethodExtensionContext} type since only at that stage
       *     arguments can be extracted
       * @return List of argument objects, empty if any error occurs
       */
      private static List<Object> getArguments(ExtensionContext context) {
        try {
          if(MethodExtensionContext.class.isAssignableFrom(context.getClass())) {
            return emptyList();
          }
          Method method = findMethod(context.getClass(), "getTestDescriptor").orElseThrow();
          TestMethodTestDescriptor descriptor =
              (TestMethodTestDescriptor) invokeMethod(method, context);
    
          // Get the TestTemplateInvocationContext
          Field templateField = descriptor.getClass().getDeclaredField("invocationContext");
          templateField.setAccessible(true);
          TestTemplateInvocationContext template =
              (TestTemplateInvocationContext) templateField.get(descriptor);
    
          // Get the params finally
          Field argumentsField = template.getClass().getDeclaredField("arguments");
          argumentsField.setAccessible(true);
          return asList((Object[]) argumentsField.get(template));
        } catch (Throwable ignored) {
          return emptyList();
        }
      }