javaunit-testingtestngtestng-dataprovider

Is there a way to give a specific name to test case using TestNG data providers?


I am writing unit tests in Java with TestNG 6.14.3 using @DataProvider

What I want to achieve:

  1. I want to give each data provider case a name.
  2. I do not want to the name to be one of the parameters but a custom name. (just an example: "Good flow no auth")

What I have tried:

  1. I have added an extra parameter and followed this guide, the issue with that is that now I have an unused parameter caseName in every test which I do not want.

  2. Create a custom DataProvider annotation which ignore the first parameter, this did not work since I can not find a way to integrate it with TestNG.

My questions:

  1. Is there a a built in way to give a specific name to a test case?
  2. If the answer to 1 is "no": is there a way to integrate a custom DataProvider or intercept the data before it is provided to the test itself?
  3. Even after following this guide I still see the other parameters as the test name postfix in Intellij, is that normal? if not I will post my example code.

Thank you!


Solution

  • One solution might be to declare a String array of names in the test class and take then in order. This would work if you are NOT running your test in parallel.

    Otherwise, you may make use of @Factory annotation, which could take the input from data provider and then create the test object.

    @Factory(dataProvider = "data")
    public Object[] createInstances(String val, String name) {
        return new Object[] { new MyTest(name) };
    }
    
    @DataProvider
    public Object[][] data() {
        return new Object[][] { { "a", "one" }, { "b", "two" } };
    }
    

    So you need a name field in your test class, which would be set using the constructor. Combining the code suggested in the link to set the test name you could do as:

    public class MyTest implements ITest {
    
        private String name;
        private ThreadLocal<String> testName = new ThreadLocal<>();
    
        public MyTest() {
        }
    
        public MyTest(String name) {
            this.name = name;
        }
    
        @Factory(dataProvider = "data")
        public Object[] createInstances(String name) {
            return new Object[] { new MyTest(name) };
        }
        
        @DataProvider
        public Object[][] data() {
            return new Object[][] { { "one" }, { "two" } };
        }
    
        @BeforeMethod
        public void beforeMethod(Method method) {
            testName.set(method.getName() + "_" + name);
        }
    
        @Test
        public void test() {
            System.out.println(name);
        }
    
        @Override
        public String getTestName() {
            return testName.get();
        }
    }
    

    EDIT: Without using a Factory, this could be achieved as below. This requires setting the relevant details in the ITestContext:

    @DataProvider
    public Object[][] data(ITestContext ctx) {
        ctx.setAttribute("names", new String[]{"one", "two"});
        ctx.setAttribute("index", 0);
        
        return new Object[][] { {"val1", "data1"}, {"val2", "data2"} };
    }
    

    Now in the beforeMethod inject ITestContext as well:

    @BeforeMethod
    public void beforeMethod(ITestContext ctx, Method method) {
        testName.set(method.getName() + "_" + getName(ctx));
    }
    
    // Create a helper method getName.
    private static String getName(ITestContext ctx) {
        int index = (int) ctx.getAttribute("index");
        // get the name based on the current index.
        String name = ((String[]) ctx.getAttribute("names"))[index++];
        
        // update the attribute with the incremented index.
        ctx.setAttribute("index", index);
        return name;
    }
    
    @Test(dataProvider = "data") 
    public void yourTest(String val, String data) {
        // .............
    }
    

    NOTE: This will not work as expected when test is run parallelly.