2

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!

Gautham M
  • 4,816
  • 3
  • 15
  • 37

2 Answers2

1

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.

Gautham M
  • 4,816
  • 3
  • 15
  • 37
1

You can do something like this:

public class TestName {

    @DataProvider(name = "nameAndOthers")
    Iterator<Object[]> provideData(ITestContext testContext){
        Object[][] data = new Object[][]{
                {"name1", 1, 2},
                {"name2", 3, 4},
                {"name3", 5, 6},
        };
        Iterator<Object[]> originalDataIterator = Arrays.asList(data).iterator();
        return new Iterator<Object[]>() {
            @Override
            public boolean hasNext() {
                return originalDataIterator.hasNext();
            }

            @Override
            public Object[] next() {
                Object[] next = originalDataIterator.next();
                testContext.getCurrentXmlTest().setName(next[0].toString());
                return Arrays.copyOfRange(next, 1, next.length);
            }
        };

    }

    @Test(dataProvider = "nameAndOthers")
    void testName(Integer one, Integer two){
        System.out.println(one + ": " + two);
    }

}

The output would be:

1: 2
3: 4
5: 6

and the tests would take their names from your data:

enter image description here

Alexey R.
  • 8,057
  • 2
  • 11
  • 27
  • thank you for your answer! Is there a way to use this code without duplicating it for each data provider? For example a custom data provider? – Itzhak Eretz Kdosha Sep 23 '21 at 06:21
  • The magic actually happens inside `Iterator`. Test name is set up each time TestNg fetches next row of data. This iterator is created as anonymous class in my example. You can just have your class implemented separately and create an instance inside your data provider. Hence you will not duplicate code. – Alexey R. Sep 23 '21 at 09:02