Randomizing TestNG Invocations - unit-testing

Similar to: Run TestNG tests in random order but different as this question does not include invocations.
TestNG randomizes the tests themselves but the invocations will all run in a row. For Example:
#Test(invocation=2) public void A{}
#Test(invocation=2) public void B{}
will either run AABB or BBAA
but I was hoping to have them run in a truly random manner (ABAB, BABA, ect)
Is there anyway to accomplish this with TestNG, at the moment all I can think of is changing my tests to methods called by one test that controls the order but I hoping for something built in to TestNG that I overlooked.

You can build on the same concept of leveraging a IMethodInterceptor.
Here's how you do it.
First you build a custom annotation that can be used to express the invocation count.
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
#Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
#Target({METHOD, TYPE})
public #interface InvocationCount {
int value() default 1;
}
You now customize your method interceptor such that it looks up the custom annotation and based on it, it adds up more instances and then eventually randomizes the resultant collection.
import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ITestContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class LocalInterceptor implements IMethodInterceptor {
#Override
public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
List<IMethodInstance> altered = new ArrayList<>();
for (IMethodInstance method : methods) {
InvocationCount count = method.getMethod().getConstructorOrMethod().getMethod().getAnnotation(InvocationCount.class);
if (count == null) {
altered.add(method);
} else {
if (count.value() > 1) {
for (int i = 0; i < count.value(); i++) {
altered.add(method);
}
}
}
}
long seed = System.nanoTime();
Collections.shuffle(altered, new Random(seed));
return altered;
}
}
Your test would now look like below
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
#Listeners(LocalInterceptor.class)
public class SampleTestClass {
#InvocationCount(2)
#Test
public void A() {
System.err.println("A()");
}
#InvocationCount(3)
#Test
public void B() {
System.err.println("B()");
}
}
Here's a sample output (There were some StringIndexOutOfBoundsException exceptions from the reporters, but I think that's not related to this)
...
... TestNG 6.14.2 by Cédric Beust (cedric#beust.com)
...
B()
A()
B()
B()
A()
PASSED: B
PASSED: A
PASSED: B
PASSED: B
PASSED: A
===============================================
48715503_test
Tests run: 5, Failures: 0, Skips: 0
===============================================
===============================================
48715503_Suite
Total tests run: 5, Failures: 0, Skips: 0
===============================================

Related

Junit5 test suite with Kotlin

Based on answer in this link, created a test suite with Test classes
#RunWith(Suite::class)
#Suite.SuiteClasses(
DTOtoEntityMapperTest::class,
PostDataSourceCoroutinesTest::class,
PostDataSourceRxJava3Test::class
)
class JUnit5TestSuite
Returns error
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.x.DTOtoEntityMapperTest':
1. No runnable methods
But every test class added has test methods and runs individually for instance
class DTOtoEntityMapperTest {
private val postDTOList by lazy {
convertFromJsonToObjectList<PostDTO>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
private val postEntityList by lazy {
convertFromJsonToObjectList<PostEntity>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
#Test
fun `given PostDTO is input, should return PostEntity`() {
val mapper = DTOtoEntityMapper()
// GIVEN
val expected = postEntityList
// WHEN
val actual = mapper.map(postDTOList)
// THEN
Truth.assertThat(actual).containsExactlyElementsIn(expected)
}
}
You can now run Suites purely with jUnit 5 engines:
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.Suite
#Suite
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}
You'll have to import the junit-platform-suite-engine:
https://search.maven.org/artifact/org.junit.platform/junit-platform-suite-engine
There's some docs here:
https://junit.org/junit5/docs/snapshot/user-guide/#launcher-api-engines-custom
https://junit.org/junit5/docs/snapshot/api/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html
Here's an official example:
https://github.com/junit-team/junit5/blob/main/documentation/src/test/java/example/SuiteDemo.java
For JUnit 5 I used JUnitPlatform and SelectClasses (or SelectPackages):
#RunWith(JUnitPlatform::class)
#SelectClasses(Some1Test::class, Some2Test::class)
class SuiteTest
After checking for a few days, I found correctly libs to use Junit 5 test suite with kotlin
In build.gradle, add 2 libs as below:
testImplementation ("org.junit.platform:junit-platform-suite-api:1.7.0")
testImplementation ("org.junit.platform:junit-platform-runner:1.2.0")
Then you can use test suite as below:
import org.junit.platform.runner.JUnitPlatform
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.SelectPackages
import org.junit.runner.RunWith
#RunWith(JUnitPlatform::class)
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}

KotlinTest with Koin: InvocationTargetException

I'm unable to use Koin 2.0.1 with Kotlin-test 3.4.2. I get an InvocationTargetException like this:
Running koinexample.KoinSampleTests
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.009 sec <<< FAILURE! - in koinexample.KoinSampleTests
koinexample.KoinSampleTests Time elapsed: 0.009 sec <<< ERROR!
java.lang.reflect.InvocationTargetException
at koinexample.KoinSampleTests.getKoin(KoinSampleTests.kt:26)
at koinexample.KoinSampleTests.<init>(KoinSampleTests.kt:61)
I've created a small example on GitHub that reproduces this error:
https://github.com/elifarley/kotlin-tests-with-koin-examples
Just execute these commands to clone the repo and run tests:
git clone https://github.com/elifarley/kotlin-tests-with-koin-examples.git
cd kotlin-tests-with-koin-examples
mvn
Here's the main Kotlin file:
package koinexample
import io.kotlintest.koin.KoinListener
import io.kotlintest.shouldBe
import io.kotlintest.specs.FreeSpec
import org.koin.core.inject
import org.koin.dsl.module
import org.koin.test.KoinTest
data class Stats(var ok: Long = 0, var error: Long = 0)
interface StatsServer {
fun newError(): Long
}
class StatsServerSimple(private val stats: Stats) : StatsServer {
override fun newError() = stats.error++
}
val appModule = module {
single { Stats() }
single { StatsServerSimple(get()) as StatsServer }
}
class KoinSampleTests : FreeSpec(), KoinTest {
private val modules = listOf(
appModule
)
override fun listeners() = listOf(KoinListener(modules))
val statsServer: StatsServer by inject()
init {
"Happy path" {
statsServer.newError() shouldBe 1
}
}
}
Your issue seems to be a simple import confusion.
Note that you're using import org.koin.core.inject, which is this function:
inline fun <reified T> KoinComponent.inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> =
getKoin().inject(qualifier, parameters)
It needs getKoin to work, and therefore you cannot initialize your test (The test class is initialized before Koin had a chance to start with the listener).
The correct import is import org.koin.test.inject, which translates to:
inline fun <reified T> KoinTest.inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> = lazy { get<T>(qualifier, parameters) }
Take note that this is indeed lazy, so Kotest will have a chance to initialize Koin before your tests start.
Fixing that import should resolve this issue
Seems like you're never starting the Koin app. You need to have
startKoin {
modules(appModule)
}
in your test method or in the beforeSpec/beforeTest function call of the spec.
Or use something like here:
override fun listeners() = listOf(KoinListener(appModule))
which is described in the documentation for kotlintest/kotest: https://github.com/Kotest/Kotest/blob/master/doc/extensions.md#koin

How to write single test runner for classes with different #RunWith()?

I wrote simple method that executes tests in my test classes: DataContainerTest.class, AnotherTest.class.
public static void main(String [] args) throws Exception {
Result result = JUnitCore.runClasses(DataContainerTest.class, AnotherTest.class);
System.out.println(result.getRunCount());
System.out.println("Total number of tests " + result.getRunCount());
System.out.println("Total number of tests failed: " + result.getFailureCount());
for(Failure failures : result.getFailures()){
System.out.println(failures.getMessage());
}
System.out.println(result.wasSuccessful());
}
This method doesn't work for my another class CommandsTest.class, where i'm using annotation #RunWith(PowerMockRunner.class). See output below:
1
Total number of tests 1
Total number of tests failed: 1
No runnable methods
false
Here is the sample of the CommandsTest.class
#RunWith(PowerMockRunner.class)
#PrepareForTest({Helper.class,
UtilsPlatform.class,
SessionManager.class,
DataContainerTest.class,
FieldObserver.class,
android.util.Log.class})
public class CommandsTest {
private static Commands2 commands;
private static License mockedLicense;
private static HSQL hsql;
#BeforeClass
public static void setUpStatic() throws Exception {
commands = new Commands2();
PowerMockito.mockStatic(UtilsPlatform.class);
PowerMockito.when(UtilsPlatform.isTablet()).thenReturn(true);
PowerMockito.mockStatic(android.util.Log.class);
PowerMockito.mockStatic(FieldObserver.class);
PowerMockito.doNothing().when(FieldObserver.class, "put", Mockito.anyInt(),
Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(), Mockito.any(Session.class));
hsql = PowerMockito.mock(HSQL.class);
PowerMockito.when(hsql, "OnCaseOpen", Mockito.anyInt(), Mockito.anyInt(),
Mockito.anyInt()).thenReturn(false);
mockedLicense = PowerMockito.mock(License.class);
}
#Before
public void setUp() throws Exception {
PowerMockito.mockStatic(SessionManager.class);
PowerMockito.mockStatic(Helper.class);
PowerMockito.doNothing().when(Helper.class, "writeToFile", Mockito.anyString(),
Mockito.any(SocketException.class));
PowerMockito.when(Helper.class, "getLicense").thenReturn(mockedLicense);
PowerMockito.doNothing().when(Helper.class, "fieldOpened", Mockito.anyString(), Mockito.anyString());
}
#Test
public void sendKeyCombinationEventTest_nullParameters_returnOne(){
Assert.assertEquals(1, commands.sendResponse());
}
#Test
public void sendKeyCombinationEventTest_registredGuisNotNullAndOneIsLocal_returnOne(){
Assert.assertEquals(1, commands.sendKeyCombinationEvent());
}
While pressing run button in AndroidStudio, all tests are passed but my own TestRunner cannot run tests in this class.
The best way to handle classes using #RunWith(PowerMockRunner.class) annotation is just put them into default test source of your android project and then run all tests via gradle test. You don't actually need to write own test runner.

Retrying a complete testNG class and not just #Test method on skip/failure

I have implemented IRetryAnalyzer to re-run my failed test cases in my testNG class.
public class Retry implements IRetryAnalyzer {
private int retryCount = 0;
private int maxRetryCount = 1;
private int outcome;
// Below method returns 'true' if the test method has to be retried else 'false'
//and it takes the 'Result' as parameter of the test method that just ran
public boolean retry(ITestResult result) {
//outcome=result.getStatus();
if (retryCount < maxRetryCount ) {
result.getTestContext().getFailedTests().removeResult(result);
result.getTestContext().getSkippedTests().removeResult(result);
System.out.println("Retrying test " + result.getName() + " with status "
+ getResultStatusName(result.getStatus()) + " for the " + (retryCount+1) + " time(s).");
Reporter.log("Retrying test " + result.getName() + " with status "
+ getResultStatusName(result.getStatus()) + " for the " + (retryCount+1) + " time(s).");
retryCount++;
return true;
}
return false;
}
public String getResultStatusName(int status) {
String resultName = null;
if(status==1)
resultName = "SUCCESS";
if(status==2)
resultName = "FAILURE";
if(status==3)
resultName = "SKIP";
return resultName;
}
}
Now I have two Test methods:
#Test(priority = 3, enabled = true, dependsOnMethods={"step2"})
public void step3()
{.....................some code......}
#Test(priority = 4, enabled = true,dependsOnMethods={"step3"})
public void step4() {
....some codee..}
If step 3 fails, testNG skips step 4 which is as expected. But upon re-run it executes only step 3 and even if it passed at second attempt, step 4 which was skipped is not executed.
Is there any way I can re-run my whole TestNG failed class or an alternate solution to run my dependent cases after the #Test method they depend on fails.
Thanks in advance!
Please do the following to get this to work:
Remove the logic of removing failed and skipped tests from your org.testng.IRetryAnalyzer implementation i.e., the below two lines
result.getTestContext().getFailedTests().removeResult(result);
result.getTestContext().getSkippedTests().removeResult(result);
Include this logic of removing the skipped/failed tests from within either an #AfterMethod method (or) from within an afterInvocation() of a org.testng.IInvokedMethodListener listener implementation.
Something like below :
#AfterMethod
public void afterMethod(ITestResult result) {
IRetryAnalyzer retry = result.getMethod().getRetryAnalyzer();
if (retry == null) {
return;
}
result.getTestContext().getFailedTests().removeResult(result.getMethod());
result.getTestContext().getSkippedTests().removeResult(result.getMethod());
}
(or)
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class Listener implements IInvokedMethodListener {
#Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
}
#Override
public void afterInvocation(IInvokedMethod method, ITestResult result) {
IRetryAnalyzer retry = result.getMethod().getRetryAnalyzer();
if (retry == null) {
return;
}
result.getTestContext().getFailedTests().removeResult(result.getMethod());
result.getTestContext().getSkippedTests().removeResult(result.getMethod());
}
}
If you leverage the listener path, please ensure that you wire in the listener using one of the following ways :
via #Listeners annotation (or)
via <listeners> tag (or)
via service loaders in TestNG.
For more information refer to my blog post here.
Additing to the Krishnan Mahadevan answer, you can chose to skip
result.getTestContext().getFailedTests().removeResult(result.getMethod());
If you remove a failed test method then, depending on test case (step4) will be executed even when step3 gets fail after retry.

How can I override the test method name that appears on the TestNG report?

How can I override the test name that appears on the TestNG report? I want to override the name that appears in the middle column (currently shows as the method name). Is this even possible?
I tried to do it like this, but it didn't work.
public class EchApiTest1 extends TestBase {
...
#BeforeTest
public void setUp() {
restClient = new RestClientPost();
this.setTestName( "ech: XXXXXX" );
}
And, the base class:
import org.testng.ITest;
public class TestBase implements ITest {
String testName = "";
#Override
public String getTestName() {
return this.testName;
}
public void setTestName( String name ) {
this.testName = name;
}
}
NOTE: The above code does work when I am viewing the report detail in the Jenkins TestNG plugin report, which shows the overridden test name as a string called "Instance Name:" at the beginning of the Reporter log output. Why, in this case, WHY does a "setTestName()" method alter a string labeled "Instance Name" in the report?
One answer I found had a suggestion like this but I don't know how to pass an ITestResult arg to a AfterMethod method:
#AfterMethod
public void setResultTestName( ITestResult result ) {
try {
BaseTestMethod bm = (BaseTestMethod)result.getMethod();
Field f = bm.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set( bm, bm.getMethodName() + "." + your_customized_name );
} catch ( Exception ex ) {
Reporter.log( "ex" + ex.getMessage() );
}
Thoughts?
Please find following code for set custom name of testcase in TestNG reports.
Following features are available in this code.
Dynamic execution on same test-case in multiple time
Set custom test-case name for reports
Set parallel execution of multiple test-cases execution
import java.lang.reflect.Field;
import org.testng.ITest;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.testng.internal.BaseTestMethod;
import com.test.data.ServiceProcessData;
public class ServiceTest implements ITest {
protected ServiceProcessData serviceProcessData;
protected String testCaseName = "";
#Test
public void executeServiceTest() {
System.out.println(this.serviceProcessData.toString());
}
#Factory(dataProvider = "processDataList")
public RiskServiceTest(ServiceProcessData serviceProcessData) {
this.serviceProcessData = serviceProcessData;
}
#DataProvider(name = "processDataList", parallel = true)
public static Object[] getProcessDataList() {
Object[] serviceProcessDataList = new Object[0];
//Set data in serviceProcessDataList
return serviceProcessDataList;
}
#Override
public String getTestName() {
this.testCaseName = "User custom testcase name";
// this.testCaseName = this.serviceProcessData.getTestCaseCustomName();
return this.testCaseName;
}
#AfterMethod(alwaysRun = true)
public void setResultTestName(ITestResult result) {
try {
BaseTestMethod baseTestMethod = (BaseTestMethod) result.getMethod();
Field f = baseTestMethod.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set(baseTestMethod, this.testCaseName);
} catch (Exception e) {
ErrorMessageHelper.getInstance().setErrorMessage(e);
Reporter.log("Exception : " + e.getMessage());
}
}}
Thanks
I found a "workaround" but I am hoping for a better answer. I want to be able to show this "test name" OR "instance name" value on the HTML report (not just within the Reporter.log output) and I am starting to think its not possible :
#Test(dataProvider = "restdata2")
public void testGetNameFromResponse( TestArguments testArgs ) {
this.setTestName( "ech: " + testArgs.getTestName() );
Reporter.log( getTestName() ); // this magic shows test name on report
....
With this workaround, the user can now identify which test it was by looking at the Reporter.log output but I still wish the name was more prominant.
I suspect the answer lies in writing a TestListenerAdapter that somehow overrides the ITestResult.getTestNameMethod() method? That is the holy grail I am looking for.
The ‘result’ object will automatically pass in the method setResultTestName( ITestResult result )
Make sure you put alwaysRun=true like the following when you have groups defined in your test class otherwise “AfterMethod” will not be excuted.
#AfterMethod (alwaysRun=true)