cswork / libraryOpen practice workspace

Articles

TestNG Interview Questions: Data Providers, Configuration Lifecycles, and Parallel Safety

Practice TestNG interviews with a data-provider fixture, lifecycle map, and concrete parallel test isolation repairs, including setup and teardown failures.

TestNG interview questions often start with annotation names but become useful when two parameter rows run at the same time. A DataProvider creates test invocations; configuration callbacks run at their documented scopes; a static mutable driver or shared customer record can still make those invocations interfere. The answer is to trace who owns each resource for each invocation, then explain setup, failure, and teardown under the chosen parallel mode.

Official TestNG facts in this guide come from the current TestNG user documentation. The three-row login fixture and interleaving are editorial examples, not measured TestNG output or a candidate-reported interview script. No employer-specific question frequency is claimed.

What is one DataProvider row in the lifecycle?

A method annotated @DataProvider supplies argument sets to a @Test method. TestNG’s documentation describes Object[][] and other supported forms, and @Test(dataProvider = "...") binds the method to its provider. Each row is a test invocation with its own arguments. If the provider is marked parallel = true, those invocations can execute concurrently; the default for that attribute is false.

The intuitive but dangerous shortcut is “the provider runs once, so the setup runs once.” Provider data creation and test configuration are separate concerns. @BeforeMethod runs before each test method invocation; @AfterMethod runs afterward. @BeforeClass runs before the first test method in that class, while @AfterClass runs after the class’s test methods. If the interviewer asks where to create a mutable browser session that must be independent per row, a per-invocation setup is a better default than a shared class field.

A DataProvider row is not itself a new test-class instance by contract. Do not assume instance fields are isolated simply because there are three input rows. Record the test instance, current thread, argument values, and resource identity if you need to diagnose a real run. The TestNG docs also distinguish suite, test-tag, group, class, and method configuration scopes; the word “test” in @BeforeTest refers to an XML test scope, not “before every @Test method.”

A three-row login fixture

Assume a hypothetical @DataProvider returns Alice, Bob, and Cara, each with a distinct fixture account. The test creates a browser session, logs in, checks that the signed-in account label matches its row, and quits the browser. The team turns on parallel = true to reduce suite time. The input data is immutable; the failure is a static WebDriver field used by all invocations.

InvocationInput accountIntended browserWhat a shared static field can hold
Row AAliceDriver ADriver B after Row B setup overwrites it.
Row BBobDriver BDriver B until another row overwrites it.
Row CCaraDriver CDriver C after Row C setup overwrites it.

An interleaving makes the bug concrete. Row A creates Driver A and stores it in static driver. Before A uses it, Row B creates Driver B and writes the same static field. A now sends its login command through Driver B. If A’s teardown executes next, it may quit B’s browser while B is still asserting. The resulting NoSuchSession or wrong-account failure looks like browser instability, but the causal object is the shared field.

The repair is to give each invocation an owned driver and matching teardown. A ThreadLocal can associate a driver with the current worker thread, but it is an implementation tool, not automatic test isolation. Remove the value after quit; do not carry it into another invocation on a reused thread. Better yet, pass the driver through an invocation-scoped fixture or make ownership explicit in a helper whose lifetime the test controls. Also isolate backend accounts and files: unique browser objects do not prevent two rows from editing the same server record.

Which configuration callback belongs where?

TestNG’s annotation table defines @BeforeSuite, @BeforeTest, @BeforeGroups, @BeforeClass, and @BeforeMethod, with corresponding after callbacks. Use the narrowest scope that matches the resource. A suite-level immutable configuration can be shared. A browser or mutable test record used by one parameter invocation should be allocated at that invocation’s boundary. A class-level setup may be appropriate for expensive read-only reference data, provided parallel tests cannot mutate it.

Consider the order for one row: class setup may already have run, method setup establishes this invocation’s resources, the test consumes its arguments, and method teardown releases those resources. Under parallel execution, those sequences interleave between rows. It is not valid to draw a single global sequence “Before A, Test A, After A, Before B” unless the runner is configured to serialize them.

TestNG documents inheritance order for configuration methods in base and derived classes: before methods execute from superclass downward; after methods run in reverse. A base class that starts a browser and a subclass that adds test data can be made understandable if dependencies are explicit. Hidden ordering between multiple methods at the same scope is a poor place to encode correctness. Prefer one deliberate setup sequence over relying on declaration order.

What happens when setup or a test fails?

Failure changes what can safely be assumed during teardown. If browser creation fails before a driver is assigned, @AfterMethod must not blindly call quit on a null or stale static value. It should release only resources actually created for that invocation. In a robust sketch, initialize the owned handle to absent, assign it after successful construction, and in teardown close it if present, then clear the association.

TestNG’s configuration attributes include alwaysRun. On an after configuration method, alwaysRun = true asks TestNG to invoke cleanup even when prior methods failed or were skipped under relevant conditions. It does not make unsafe cleanup code safe; teardown still needs null checks and precise ownership. Also distinguish a test assertion failure from configuration failure in reports. The latter can cause dependent tests to skip, making “zero failed assertions” a misleading green signal if the intended cases never ran.

If a shared fixture setup fails, decide whether the entire class should stop or whether each row can establish an independent fixture. Do not turn every failure into a retry. A retry that makes the suite green can hide a shared-state race and may create duplicate backend records. Preserve the first failure, invocation arguments, thread and driver identity, and setup/teardown outcome.

How do DataProvider parallelism and suite parallelism interact?

The @DataProvider(parallel = true) attribute controls concurrent data rows for that provider. TestNG also has suite-level parallel modes for methods, tests, classes, or instances, plus thread-count controls. These are different scheduling decisions. The official documentation gives an XML data-provider-thread-count example; do not hard-code its default as a timeless property of every launcher, plugin, or version. Inspect the actual runner configuration and observed thread names.

A simple concurrency bound for discussion is not “rows equals workers.” If three data rows exist but the provider pool allows two workers, at most two of those invocations can run simultaneously through that pool. Other test classes and build-tool forks can add parallelism outside that particular pool. A suite may be serial inside one JVM yet collide with another CI shard using the same account or database schema.

Treat the runner as one layer of ownership. For each mutable thing—WebDriver, temporary file, clock, system property, fixture account, database row, and remote service quota—ask whether its namespace is per invocation, per thread, per class, per process, or shared by all CI jobs. A ThreadLocal driver addresses only one process-thread edge; it does nothing for a common S3 object or user account.

A bounded code sketch and its limits

This conceptual Java sketch shows the intended lifetime without pretending a full browser integration ran in this workspace:

@DataProvider(name = "accounts", parallel = true)
public Object[][] accounts() {
    return new Object[][] { {"alice"}, {"bob"}, {"cara"} };
}

@Test(dataProvider = "accounts")
public void loginShowsAccount(String account) {
    WebDriver driver = newRemoteDriverForThisInvocation();
    try {
        loginAs(driver, account);
        assertAccountLabel(driver, account);
    } finally {
        driver.quit();
    }
}

The helper names are placeholders. A real project must configure the Grid URL, browser options, credentials from a secure test source, and deterministic account state. The local variable avoids static driver aliasing for this test method, and finally ties cleanup to the successfully created driver. If creation itself throws, the assignment never completes and there is no local driver to quit; if partial remote session creation is possible, the client and Grid still need diagnostic and cleanup procedures.

A separate @BeforeMethod/@AfterMethod design may be better when many tests share a fixture pattern. In that version, use a clearly owned per-invocation handle and make cleanup conditional. The code sketch proves a scope idea; it is not a guarantee against network flakes, shared server state, or browser crashes.

How would you reproduce the parallel failure?

Run each row alone first, then run all rows serially, then with the provider’s parallel setting. If only parallel execution fails, capture an interleaving with a barrier or a controlled fake driver that records owner and commands. Do not leave an artificial barrier in ordinary production tests, especially if the runner might later serialize execution and deadlock. The goal is a minimal demonstration of the alias.

A useful event log reads: “A assigned Driver A; B assigned Driver B to static field; A read Driver B; A teardown quit Driver B.” That is more actionable than “tests sometimes fail on Jenkins.” After the fix, assert that every row used its own driver ID, did not see another account, and released its own resources. Count discovered, started, passed, failed, and skipped invocations; a shortened suite can look fast because cases were skipped.

For external systems, seed unique data per row and remove only what that row created. If a test must validate a single shared lock or rate limit, then sharing is intentional; write an explicit concurrency contract and expected outcome. Parallel safety is about controlling interference, not forbidding all shared resources.

Fast answers to follow-up questions

Is @BeforeTest the same as @BeforeMethod? No. @BeforeTest is tied to the XML test scope; @BeforeMethod runs before each test-method invocation.

Does parallel = true on the provider isolate objects? No. It schedules invocations concurrently. Isolation comes from fixture and resource ownership.

Would synchronized on the test method fix the shared driver? It may serialize that method and hide the race, while losing useful parallelism and leaving cross-method or cross-process sharing. Repair ownership first.

Is ThreadLocal always sufficient? No. Remove values after use, consider reused workers, and isolate server-side state independently.

What does a configuration failure do to confidence? It may skip intended tests. Inspect invocation counts and configuration outcomes, not only the pass percentage.

Sources and further reading