I am working on a project where I need to invoke TestNG programatically(using data providers). Things are fine except that in the report, we are getting the name of the @Tes
I ran into a similar problem. First I implemented the ITest strategy already mentioned. And this is part of the solution, but not completely.
TestNG, for some reason, when building different reports, calls getName() on the test while building the report. This is fine if you are not using a data provider to generate different runs and set a unique name for each run by using the ITest strategy. If you are using a data provider to generate multiple runs of the same test and want each run to have a unique name then there is a problem. As the ITest strategy leaves the name for the test as the name set by the last run.
So I had to implement a very custom getName(). SOme assumptions (in my particular case):
- Only three reports are run: TestHTMLReporter, EmailableReporter, XMLSuiteResultWriter.
- When ever get name is not called as a result of one of the assumed reporters, then returning the currently set name is fine.
- When a reporter is running, it makes its getName() calls in order and only 1 time for each run.
public String getTestName()
{
String name = testName;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();//.toString();
if(calledFrom(stackTrace, "XMLSuiteResultWriter"))
{
name = testNames.size()>0?testNames.get(xmlNameIndex=testNames.size())
xmlNameIndex = 0;
}
else if(calledFrom(stackTrace, "EmailableReporter"))
{
name = testNames.size()>0?testNames.get(emailNameIndex=testNames.size())
emailNameIndex = 0;
}
if(calledFrom(stackTrace, "TestHTMLReporter"))
{
if(testNames.size()<0)
{
name = "undefined";
}
else
{
if(htmlNameIndex < testNamesFailed.size())
{
name = testNamesFailed.get(htmlNameIndex);
}
else
{
int htmlPassedIndex = htmlNameIndex - testNamesFailed.size();
if(htmlPassedIndex < testNamesPassed.size())
{
name = testNamesPassed.get(htmlPassedIndex);
}
else
{
name = "undefined";
}
}
}
htmlNameIndex++;
if(htmlNameIndex>=testNames.size())
htmlNameIndex = 0;
}
return name;
}
private boolean calledFrom(StackTraceElement[] stackTrace, String checkForMethod)
{
boolean calledFrom = false;
for(StackTraceElement element : stackTrace)
{
String stack = element.toString();
if(stack.contains(checkForMethod))
calledFrom = true;
}
return calledFrom;
}
When setting the name for the run (I did this in the @BeforeMethod(alwaysRun=true) method I defined following the ITest strategy) I added the name to an ArrayList testNames. But then the html report was not correct. Most of the other reports pulls the information in order, like the XMLSuiteResultWriter, but TestHTMLReporter gets the name by first getting all the names for failed tests and then the names for passing tests. So I had to implement to additional ArrayLists: testNamesFailed and testNamesPassed and add the test names to them when the test finished based on whether they passed or not.
And I will freely admit this is very much a hack and very fragile. Ideally, TestNG adds the tests to a collection while running, and gets the name from that collection instead of from the original test. If you have TestNG to run other reports you will have to figure out what order they request the names and what is a unique enough string to search for in the thread stack trace.
--Edit 1
Alternatively, use the ITest Strategy and the factory pattern (@factory annotations).
TestNG Using @Factory and @DataProvider
http://beust.com/weblog/2004/09/27/testngs-factory/
It does require some minor changes. This includes creating a constructor with the same parameters as the original test method. The test method now has no parameters. You can set the name in the new constructor and simply return that in the getTestName method. Make sure to remove the data provider specification from the test method.