skip to content

Your REST Assured roster suite passes class by class but fails in a full single-threaded run — how do you find the leaked static state?

level: seniorimportance: must knowfreq 49%

answer

  1. passes alone, fails in company
  2. shuffle the order first
  3. the suspect list is closed
  4. the statics are public - print them
  5. one writer, or none

basics

~20 s

Reverse or randomise the class order to confirm the failure follows position, not the test. Then print the RestAssured statics at the boundary, find the class that assigned one, and make it call RestAssured.reset() and re-apply the suite defaults.

solid answer

~40 s

An order-dependent failure in a single-threaded run is the signature of leaked static state, and in REST Assured the suspects are named: `baseURI`, `port`, `basePath`, `authentication`, `config`, `requestSpecification`, `responseSpecification`, `rootPath`, `urlEncodingEnabled`, `defaultParser`, `sessionId`, `proxy` and the default filter list. Confirm the diagnosis first by reversing or randomising class order — if the failure moves with position rather than with the test, it is state, not the roster service. Then read the statics out at the boundary, since they are public and cheap to print: `RestAssured.baseURI`, `RestAssured.basePath`, `RestAssured.filters()`, `RestAssured.requestSpecification != null`. Grep for `RestAssured.` assignments outside your one setup class; that is usually the whole hunt. Fix it by having the offending class call `RestAssured.reset()` and re-apply the suite defaults, then stop writing statics from test classes at all.

code

java · 16 lines
java
import io.restassured.RestAssured;

// drop this in front of the first request of the failing class
static void dumpRestAssuredStatics(String where) {
    System.out.println("[" + where + "] baseURI=" + RestAssured.baseURI
        + " port=" + RestAssured.port
        + " basePath=" + RestAssured.basePath
        + " rootPath='" + RestAssured.rootPath + "'"
        + " urlEncoding=" + RestAssured.urlEncodingEnabled
        + " defaultParser=" + RestAssured.defaultParser
        + " sessionId=" + RestAssured.sessionId
        + " proxy=" + RestAssured.proxy
        + " reqSpec=" + (RestAssured.requestSpecification != null)
        + " respSpec=" + (RestAssured.responseSpecification != null)
        + " filters=" + RestAssured.filters());
}

go deeper

for a junior

Recognise the symptom: a test that passes alone and fails in a full run is usually about shared state, not about the service. Say that REST Assured keeps suite-wide settings in static fields the whole JVM shares.

for a middle

Walk the mechanics. Name the closed list of RestAssured statics, explain that given() reads them fresh on every call, and show that printing them at a class boundary identifies the leak without a debugger.

for a senior

Demonstrate method: prove order dependence by shuffling, bisect to the class pair, grep for the writer, fix with reset() plus re-applied defaults, and add a teardown assertion so the regression cannot come back quietly.

for a principal

Argue the policy. Decide whether the suite gets exactly one writer of the RestAssured statics, what belongs in a specification a test owns instead, and how CI enforces the rule — shuffled order, a boundary assertion, a review gate on new global assignments.

A bell-tower roster suite that is green class by class and red as a whole, on one thread, has told you something specific before you have read a single stack trace: **the outcome of a test depends on what ran before it**. In a REST Assured suite that is nearly always the `RestAssured` class, because its configuration surface is a set of `public static` fields shared by the entire JVM and nothing scopes an assignment to a test. ## Step 1 — prove it is order dependence Do not start reading code. Establish the shape of the bug: - Run the failing class alone. If it passes, the class is not wrong. - Run the full suite with the class order reversed or shuffled. If the failure moves to a different class, or disappears, the failure follows **position**, not the test. - Run the failing class immediately after each suspected class in turn to find the pair. This is a bisection over classes and it converges quickly. - Check that the roster service itself is not the variable — the same test against the same data passing alone rules that out. ## Step 2 — read the statics at the boundary The statics are public, so a diagnosis costs four lines rather than a debugger session. Print them immediately before the first request of the failing class: ```java System.out.println(RestAssured.baseURI); // http://localhost ? System.out.println(RestAssured.basePath); // "/api/v1" ? System.out.println(RestAssured.port); // -1 or something pinned ? System.out.println(RestAssured.requestSpecification != null); System.out.println(RestAssured.filters()); System.out.println(RestAssured.defaultParser + " " + RestAssured.proxy); ``` The list of things that can be wrong is short and closed. Walk it: `baseURI`, `port`, `basePath`, `authentication`, `config`, `requestSpecification`, `responseSpecification`, `rootPath`, `urlEncodingEnabled`, `defaultParser`, `sessionId`, `proxy`, and the default filter list. One of them will not be what your setup class left there. ## Step 3 — find the writer Now the grep is targeted. Search the suite for assignments to those names outside the one place that is allowed to make them: - `RestAssured.baseURI =`, `RestAssured.basePath =`, `RestAssured.port =` and the rest of the field assignments. - `RestAssured.filters(` and `RestAssured.replaceFiltersWith(` — a registered filter leaks with no visible setting at all, which makes it the hardest case to spot by eye. - `RestAssured.requestSpecification =` — the most damaging one, because it silently applies a whole specification to every later request. - `RestAssured.registerParser(` and `RestAssured.defaultParser =`, which change how bodies are read for the rest of the run. ## Step 4 — fix it structurally The immediate fix is `RestAssured.reset()` in the offending class's teardown, followed by re-applying the suite-wide defaults, because `reset()` deletes those too — it empties the filter list and drops parser registrations along with everything else. Two details save you a second bug here: 1. `reset()` sets `port` to `UNDEFINED_PORT` (`-1`), not `DEFAULT_PORT` (`8080`), so do not assert `8080` afterwards. 2. `reset()` cannot reach an object a class already built. A `RequestSpecification` or `RequestSpecBuilder` created earlier snapshotted the statics and keeps its copy regardless. The durable fix is to stop writing statics from test classes. Assign the `RestAssured` statics in exactly one place for the whole run, make everything else that varies a `RequestSpecification` the test itself holds and passes in, and treat a new `RestAssured.` assignment in review as something that must be argued for. A suite with one writer has no order dependence to diagnose. ## Step 5 — make the regression visible Once fixed, keep it fixed with a cheap guard rather than a convention: - Assert the invariant in a suite-level teardown: `assertEquals("https://roster.belltower.test", RestAssured.baseURI)` and `assertEquals(1, RestAssured.filters().size())`. - Keep class order shuffled in CI so a new leak surfaces on the pull request that introduced it rather than three weeks later. - Log the resolved statics once at the start of the run, so any red build carries the evidence in its own output. ## What `reset()` will not save you from `reset()` only assigns fields on `RestAssured`. It does nothing about a specification object a class is holding, nothing about `JsonSchemaValidator.settings` in the `json-schema-validator` module — a separate static holder with its own `JsonSchemaValidator.reset()` — nothing about state your own harness caches, and nothing about roster data your tests created on the server. If the failure survives a correct `reset()`, the leaked state is somewhere other than REST Assured's entry surface, and the same bisection method applies to whatever holder is next on the list.

  • Which leaked RestAssured static is hardest to spot by reading a failing test, and why?
    A default filter registered with `RestAssured.filters(...)`. Every other leak shows up as a wrong value in a field you can print by name, but a filter leaves no setting behind at all — it just adds behaviour to requests that never asked for it. `RestAssured.filters()` is the only way to see it, which is why printing it at the boundary belongs in the checklist.
  • The failure survives a correct RestAssured.reset() in teardown. Where do you look next?
    Somewhere other than REST Assured's entry surface. `reset()` assigns fields on `RestAssured` and nothing else, so check specification objects a class built earlier and still holds, `JsonSchemaValidator.settings` with its own `reset()`, caches in your own harness, and roster data a previous class created on the server. The bisection over class order still finds the pair.

saying these in an interview costs you the question

  • Blames the roster service before checking class order
  • Adds a retry to a suite that is order dependent
  • Calls reset() but never re-applies the suite defaults
  • Assumes reset() clears a specification a class already built
  • Sets a RestAssured static in a test method to work around it
  • Pins class order so the suite passes and stops there