Basic Annotations
TestNG uses annotations to control the flow and behavior of test execution. These annotations are the foundation of TestNG's functionality and provide a declarative way to configure your tests. In this section, we'll explore the core annotations that form the basis of TestNG testing.
@Test
The @Test annotation is the most fundamental annotation in TestNG. It marks a method as a test method that should be executed by the TestNG framework.
import org.testng.annotations.Test;
public class BasicAnnotationDemo {
@Test
public void simpleTest() {
// Test logic goes here
System.out.println("This is a simple test method");
}
}
The @Test annotation can also be configured with various attributes to control test behavior:
import org.testng.annotations.Test;
public class TestAttributesDemo {
@Test(description = "This is a test with a custom description")
public void testWithDescription() {
// Test logic
}
@Test(enabled = false)
public void disabledTest() {
// This test will be skipped
}
@Test(timeOut = 1000) // Timeout in milliseconds
public void testWithTimeout() {
// Test must complete within 1 second
}
@Test(priority = 1)
public void highPriorityTest() {
// Lower priority value means higher execution priority
}
@Test(invocationCount = 5)
public void repeatedTest() {
// This test will run 5 times
}
}
Test Configuration Annotations
TestNG provides a set of annotations that allow you to configure setup and teardown operations at different levels of test execution. These annotations help in preparing the test environment, initializing resources, and cleaning up after tests.
@BeforeMethod and @AfterMethod
These annotations mark methods that should run before and after each test method in a test class.
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MethodLevelSetupDemo {
@BeforeMethod
public void beforeEachTest() {
System.out.println("Setting up test environment");
// Initialize resources for the test
}
@Test
public void testOne() {
System.out.println("Executing test one");
}
@Test
public void testTwo() {
System.out.println("Executing test two");
}
@AfterMethod
public void afterEachTest() {
System.out.println("Cleaning up test environment");
// Release resources after the test
}
}
@BeforeClass and @AfterClass
These annotations mark methods that should run once before the first test method and after the last test method in the current class.
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class ClassLevelSetupDemo {
@BeforeClass
public void beforeAllTests() {
System.out.println("Setting up class-level resources");
// Initialize resources shared by all tests in the class
}
@Test
public void testOne() {
System.out.println("Executing test one");
}
@Test
public void testTwo() {
System.out.println("Executing test two");
}
@AfterClass
public void afterAllTests() {
System.out.println("Cleaning up class-level resources");
// Release shared resources
}
}
@BeforeTest and @AfterTest
These annotations mark methods that should run before and after all test methods belonging to the classes inside the <test> tag in a TestNG XML file.
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
public class TestLevelSetupDemo {
@BeforeTest
public void beforeTestGroup() {
System.out.println("Setting up test-level resources");
// Initialize resources for all tests in the <test> tag
}
@Test
public void testOne() {
System.out.println("Executing test one");
}
@Test
public void testTwo() {
System.out.println("Executing test two");
}
@AfterTest
public void afterTestGroup() {
System.out.println("Cleaning up test-level resources");
// Release resources for all tests in the <test> tag
}
}
@BeforeSuite and @AfterSuite
These annotations mark methods that should run before and after all tests in the suite.
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
public class SuiteLevelSetupDemo {
@BeforeSuite
public void beforeAllSuites() {
System.out.println("Setting up suite-level resources");
// Initialize resources for the entire test suite
}
@Test
public void testOne() {
System.out.println("Executing test one");
}
@Test
public void testTwo() {
System.out.println("Executing test two");
}
@AfterSuite
public void afterAllSuites() {
System.out.println("Cleaning up suite-level resources");
// Release resources for the entire test suite
}
}
Test Execution Control Annotations
TestNG provides annotations that allow you to control the execution flow of your tests, such as defining dependencies, grouping tests, and ignoring tests.
@DependsOnMethods and @DependsOnGroups
These annotations allow you to specify dependencies between test methods or groups, ensuring that tests run in the correct order.
import org.testng.annotations.Test;
public class DependencyDemo {
@Test
public void loginTest() {
System.out.println("Executing login test");
// Test login functionality
}
@Test(dependsOnMethods = {"loginTest"})
public void dashboardTest() {
System.out.println("Executing dashboard test");
// Test dashboard functionality after login
}
@Test(dependsOnMethods = {"dashboardTest"})
public void logoutTest() {
System.out.println("Executing logout test");
// Test logout functionality
}
}
@Groups
This annotation allows you to categorize test methods into groups, which can be selectively executed.
import org.testng.annotations.Test;
public class GroupsDemo {
@Test(groups = {"smoke"})
public void smokeTest1() {
System.out.println("Executing smoke test 1");
}
@Test(groups = {"smoke"})
public void smokeTest2() {
System.out.println("Executing smoke test 2");
}
@Test(groups = {"regression"})
public void regressionTest1() {
System.out.println("Executing regression test 1");
}
@Test(groups = {"regression"})
public void regressionTest2() {
System.out.println("Executing regression test 2");
}
@Test(groups = {"smoke", "regression"})
public void bothTest() {
System.out.println("Executing test in both smoke and regression");
}
}
@BeforeGroups and @AfterGroups
These annotations mark methods that should run before and after the first and last test method in a specific group.
import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
public class GroupSetupDemo {
@BeforeGroups({"database"})
public void setupDatabaseConnection() {
System.out.println("Setting up database connection");
// Initialize database connection
}
@Test(groups = {"database"})
public void testDatabaseQuery1() {
System.out.println("Testing database query 1");
}
@Test(groups = {"database"})
public void testDatabaseQuery2() {
System.out.println("Testing database query 2");
}
@AfterGroups({"database"})
public void closeDatabaseConnection() {
System.out.println("Closing database connection");
// Close database connection
}
}
@Ignore
This annotation marks a test method to be ignored during test execution.
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;
public class IgnoreDemo {
@Test
public void activeTest() {
System.out.println("This test will run");
}
@Ignore
@Test
public void ignoredTest() {
System.out.println("This test will be ignored");
}
@Test(enabled = false) // Alternative way to ignore a test
public void disabledTest() {
System.out.println("This test will also be ignored");
}
}
Parameterization Annotations
TestNG provides annotations for parameterizing tests, allowing you to run the same test with different data sets.
@Parameters
This annotation allows you to pass parameters from the TestNG XML file to test methods.
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
public class ParametersDemo {
@Parameters({"username", "password"})
@Test
public void loginTest(String username, String password) {
System.out.println("Logging in with username: " + username + " and password: " + password);
// Test login with the provided credentials
}
}
Corresponding TestNG XML file:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="ParameterizedSuite">
<test name="ParameterizedTest">
<parameter name="username" value="testuser"/>
<parameter name="password" value="testpass"/>
<classes>
<class name="ParametersDemo"/>
</classes>
</test>
</suite>
@DataProvider
This annotation marks a method as a data provider that supplies data to test methods.
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class DataProviderDemo {
@DataProvider(name = "calculatorData")
public Object[][] createData() {
return new Object[][] {
{5, 3, 8}, // a, b, expected sum
{10, 5, 15},
{-3, 3, 0},
{0, 0, 0}
};
}
@Test(dataProvider = "calculatorData")
public void testAddition(int a, int b, int expected) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
Assert.assertEquals(result, expected, "Addition result is incorrect");
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
@Factory
This annotation marks a method that returns objects that will be used by TestNG as test classes.
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class FactoryDemo {
@Factory
public Object[] createInstances() {
Object[] instances = new Object[2];
instances[0] = new FactoryTestClass(10);
instances[1] = new FactoryTestClass(20);
return instances;
}
}
class FactoryTestClass {
private int parameter;
public FactoryTestClass(int parameter) {
this.parameter = parameter;
}
@Test
public void testMethod() {
System.out.println("Test with parameter: " + parameter);
}
}
Java 21 Integration with TestNG Annotations
Java 21 introduces several features that can enhance the way you use TestNG annotations. Let's explore some examples:
Using Record Patterns with @DataProvider
Java 21's record patterns can be used to create more structured test data:
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class RecordPatternDataProviderDemo {
record TestData(int a, int b, int expected) {}
@DataProvider(name = "calculatorData")
public Object[][] createData() {
return new Object[][] {
{new TestData(5, 3, 8)},
{new TestData(10, 5, 15)},
{new TestData(-3, 3, 0)},
{new TestData(0, 0, 0)}
};
}
@Test(dataProvider = "calculatorData")
public void testAddition(TestData data) {
// Using record pattern for destructuring
if (data instanceof TestData(int a, int b, int expected)) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
Assert.assertEquals(result, expected, "Addition result is incorrect");
}
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Using Virtual Threads with @BeforeClass
Java 21's virtual threads can be used to perform setup operations more efficiently:
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.List;
public class VirtualThreadSetupDemo {
private List<String> setupResults = new ArrayList<>();
@BeforeClass
public void parallelSetup() throws Exception {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
// Submit multiple setup tasks to run concurrently
futures.add(executor.submit(() -> {
// Setup task 1
Thread.sleep(1000); // Simulate work
return "Setup 1 complete";
}));
futures.add(executor.submit(() -> {
// Setup task 2
Thread.sleep(1000); // Simulate work
return "Setup 2 complete";
}));
futures.add(executor.submit(() -> {
// Setup task 3
Thread.sleep(1000); // Simulate work
return "Setup 3 complete";
}));
// Collect all results
for (Future<String> future : futures) {
setupResults.add(future.get());
}
}
System.out.println("All setup tasks completed concurrently");
}
@Test
public void testAfterSetup() {
System.out.println("Setup results: " + setupResults);
// Test logic using the setup results
}
}
Using Pattern Matching for Switch with @Test
Java 21's pattern matching for switch can simplify test logic:
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class PatternMatchingSwitchDemo {
@DataProvider(name = "shapes")
public Object[][] createShapes() {
return new Object[][] {
{new Circle(5.0)},
{new Rectangle(4.0, 5.0)},
{new Triangle(3.0, 4.0)}
};
}
@Test(dataProvider = "shapes")
public void testAreaCalculation(Shape shape) {
double expectedArea = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
};
double actualArea = shape.calculateArea();
Assert.assertEquals(actualArea, expectedArea, 0.001, "Area calculation is incorrect");
}
}
sealed interface Shape permits Circle, Rectangle, Triangle {
double calculateArea();
}
record Circle(double radius) implements Shape {
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double calculateArea() {
return width * height;
}
}
record Triangle(double base, double height) implements Shape {
@Override
public double calculateArea() {
return 0.5 * base * height;
}
}