Excluding a non param test in parameterized test class

JavaJunitParameterized

Java Problem Overview


Is there any annotation in JUnit to exclude a non param test in parameterized test class?

Java Solutions


Solution 1 - Java

JUnit 5

As of Junit 5.0.0 you can now annotate your test methods with @ParameterizedTest. So no need for inner classes. There are many ways to supply the arguments to the parameterized test apart from ValueSource as shown below. See the official junit user guide for details:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class ComponentTest {
        
    @ParameterizedTest
    @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
    public void testCaseUsingParams(String candidate) throws Exception {
    }
        
    @Test
    public void testCaseWithoutParams() throws Exception {
    }
}

JUnit 4

If you are still using Junit 4 (I tested with v4.8.2) you can use the Enclosed runner in conjunction with inner classes and the Parameterized runner:

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Enclosed.class)
public class ComponentTest {

    @RunWith(Parameterized.class)
    public static class ComponentParamTests {

        @Parameters
        ...
        
        @Test
        public void testCaseUsingParams() throws Exception {
        }
    }

    public static class ComponentSingleTests {
        
        @Test
        public void testCaseWithoutParams() throws Exception {
        }
    }
}

Solution 2 - Java

No. The best practice is to move those non-parameterized tests to a different class (.java file)

Solution 3 - Java

Zohhak test runner is a simpler way to parameterize specific tests. Thanks Piotr!

Solution 4 - Java

I was able to do something very similar to Matthew Madson answer and found it useful to create a Base Class to encapsulate setup and common helper functions between the single and param tests. This works without using Enclosed.class.

 @RunWith(Suite.class)
 @SuiteClasses({ComponentTest.ComponentParamTests.class, ComponentTest.ComponentSingleTests.class})
 public class ComponentTest {

    public static class TestBase {
        @Spy
        ...
        @Before
        ...
    }
    
    @RunWith(Parameterized.class)
    public static class ComponentParamTests extends TestBase{
        @Parameter
        ...
        @Parameters
        ...
        @Test
        ...
    }
    public static class ComponentSingleTests extends TestBase{
    	@Test
        ...
    }
}

Solution 5 - Java

It appears that TestNG does not suffer from this problem. I'm not that desperate so I modified the builtin Parameterized class to support this feature. Just annotate applicable tests as @NonParameterized. Note that this class only works with its on annotations, i.e. check your imports.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.junit.Test;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.Suite;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;

/**
 * <p>
 * The custom runner <code>Parameterized</code> implements parameterized tests.
 * When running a parameterized test class, instances are created for the
 * cross-product of the test methods and the test data elements.
 * </p>
 * For example, to test a Fibonacci function, write:
 *
 * <pre>
 * &#064;RunWith(Parameterized.class)
 * public class FibonacciTest {
 *     &#064;Parameters
 *     public static List&lt;Object[]&gt; data() {
 *         return Arrays.asList(new Object[][] {
 *                 Fibonacci,
 *                 { {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5},
 *                         {6, 8}}});
 *     }
 *
 *     private int fInput;
 *
 *     private int fExpected;
 *
 *     public FibonacciTest(int input, int expected) {
 *         fInput = input;
 *         fExpected = expected;
 *     }
 *
 *     &#064;Test
 *     public void test() {
 *         assertEquals(fExpected, Fibonacci.compute(fInput));
 *     }
 * }
 * </pre>
 * <p>
 * Each instance of <code>FibonacciTest</code> will be constructed using the
 * two-argument constructor and the data values in the
 * <code>&#064;Parameters</code> method.
 * </p>
 */
public class Parameterized extends Suite {

    /**
     * Annotation for a method which provides parameters to be injected into the
     * test class constructor by <code>Parameterized</code>
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public static @interface Parameters {
    }

    /**
     * Annotation for a methods which should not be parameterized
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public static @interface NonParameterized {
    }

    private class TestClassRunnerForParameters extends
            BlockJUnit4ClassRunner {
        private final int fParameterSetNumber;

        private final List<Object[]> fParameterList;

        TestClassRunnerForParameters(Class<?> type,
                List<Object[]> parameterList, int i) throws InitializationError {
            super(type);
            fParameterList = parameterList;
            fParameterSetNumber = i;
        }

        @Override
        public Object createTest() throws Exception {
            return getTestClass().getOnlyConstructor().newInstance(
                    computeParams());
        }

        private Object[] computeParams() throws Exception {
            try {
                return fParameterList.get(fParameterSetNumber);
            } catch (ClassCastException e) {
                throw new Exception(String.format(
                        "%s.%s() must return a Collection of arrays.",
                        getTestClass().getName(), getParametersMethod(
                                getTestClass()).getName()));
            }
        }

        @Override
        protected String getName() {
            return String.format("[%s]", fParameterSetNumber);
        }

        @Override
        protected String testName(final FrameworkMethod method) {
            return String.format("%s[%s]", method.getName(),
                    fParameterSetNumber);
        }

        @Override
        protected void validateConstructor(List<Throwable> errors) {
            validateOnlyOneConstructor(errors);
        }

        @Override
        protected Statement classBlock(RunNotifier notifier) {
            return childrenInvoker(notifier);
        }

        @Override
        protected List<FrameworkMethod> computeTestMethods() {
            List<FrameworkMethod> ret = super.computeTestMethods();
            for (Iterator<FrameworkMethod> i = ret.iterator(); i.hasNext();) {
                FrameworkMethod frameworkMethod =
                    (FrameworkMethod) i.next();
                if (isParameterized() ^
                    !frameworkMethod.getMethod().isAnnotationPresent(
                        NonParameterized.class)) {
                    i.remove();
                }
            }
            return ret;
        }

        protected boolean isParameterized() {
            return true;
        }
    }

    private class TestClassRunnerForNonParameterized extends
        TestClassRunnerForParameters {

        TestClassRunnerForNonParameterized(Class<?> type,
            List<Object[]> parameterList, int i)
            throws InitializationError {
            super(type, parameterList, i);
        }

        protected boolean isParameterized() {
            return false;
        }
    }

    private final ArrayList<Runner> runners = new ArrayList<Runner>();

    /**
     * Only called reflectively. Do not use programmatically.
     */
    public Parameterized(Class<?> klass) throws Throwable {
        super(klass, Collections.<Runner> emptyList());
        List<Object[]> parametersList = getParametersList(getTestClass());
        if (parametersList.size() > 0) {
            try {
                runners.add(new TestClassRunnerForNonParameterized(
                    getTestClass()
                        .getJavaClass(), parametersList, 0));
            } catch (Exception e) {
                System.out.println("No non-parameterized tests.");
            }
        }
        try {
            for (int i = 0; i < parametersList.size(); i++) {
                runners.add(new TestClassRunnerForParameters(getTestClass()
                    .getJavaClass(),
                    parametersList, i));
            }
        } catch (Exception e) {
            System.out.println("No parameterized tests.");
        }
    }

    @Override
    protected List<Runner> getChildren() {
        return runners;
    }

    @SuppressWarnings("unchecked")
    private List<Object[]> getParametersList(TestClass klass)
            throws Throwable {
        return (List<Object[]>) getParametersMethod(klass).invokeExplosively(
                null);
    }

    private FrameworkMethod getParametersMethod(TestClass testClass)
            throws Exception {
        List<FrameworkMethod> methods = testClass
                .getAnnotatedMethods(Parameters.class);
        for (FrameworkMethod each : methods) {
            int modifiers = each.getMethod().getModifiers();
            if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
                return each;
        }

        throw new Exception("No public static parameters method on class "
                + testClass.getName());
    }

}

Update: I'm trying to get this sort of thing added to junit.

Solution 6 - Java

For those who wants the parameters come from java function instead of annotations:

@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
    assertEquals(expected, Strings.isBlank(input));
}

private static Stream<Arguments> provideStringsForIsBlank() {
    return Stream.of(
      Arguments.of(null, true),
      Arguments.of("", true),
      Arguments.of("  ", true),
      Arguments.of("not blank", false)
    );
}

source: https://www.baeldung.com/parameterized-tests-junit-5

Solution 7 - Java

Assuming you use Parametrized.class to run your test class - mark all non-parametrized tests as @Ignored. Otherwise you can make a static inner class to group all your parametrized tests and another - for non-parametrized.

Solution 8 - Java

I did something similar to Matthew's Solution. However, I created two new java files that extends the current file so that the ComponentSingleTests do not run twice. This way, they can share common member variables and helper methods from the parent class. The problem I had with Matthew's Solution was that my single test was running twice instead of once as the Enclosed.class (which extends the Suite.class) will make your test run twice as described in this link https://stackoverflow.com/questions/34741217/prevent-junit-tests-from-running-twice

ComponentTest.java

public class ComponentTest {
    public int sharedMemberVariables; 
    ... 
}

ComponentParamTests.java

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class ComponentParamTests extends ComponentTest {

    @Parameters
    ...

    @Test
    public void testCaseUsingParams() throws Exception {
    }
}

ComponentSingleTests.java

import org.junit.Test;

public class ComponentSingleTests {

    @Test
    public void testCaseWithoutParams() throws Exception {
        ...
    }
}

Solution 9 - Java

I stuck in this problem while I'm writing test in spring boot MockMvc I simply created two classes in separate java files ( one for ParameterizedTest and other for SingleTest ) and create a suite for them. because inner classes were creating error for static members and and not static members and class.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser405516View Question on Stackoverflow
Solution 1 - JavaMatthew MadsonView Answer on Stackoverflow
Solution 2 - JavaJeanne BoyarskyView Answer on Stackoverflow
Solution 3 - JavaAmanicAView Answer on Stackoverflow
Solution 4 - JavaPhil NinanView Answer on Stackoverflow
Solution 5 - JavaAmanicAView Answer on Stackoverflow
Solution 6 - Javach271828nView Answer on Stackoverflow
Solution 7 - JavaArtem OboturovView Answer on Stackoverflow
Solution 8 - JavaChee Loong SoonView Answer on Stackoverflow
Solution 9 - JavaMuhammad AzamView Answer on Stackoverflow