In Cucumber-JVM, what does each hook level — @Before, @BeforeStep, @BeforeAll — wrap, and which still run after a step fails?
answer
- three scopes, not one
- scenario, step, whole run
- teardown must survive a failure
- the run-level pair is static
- one Examples row is one scenario
basics
~20 s@Before and @After wrap one scenario, @BeforeStep and @AfterStep wrap every individual step, and @BeforeAll and @AfterAll run once per JVM around the whole run. @After still runs when a step or a @Before hook fails.
solid answer
~50 sCucumber-JVM has three nested hook scopes, all in `io.cucumber.java`. `@Before`/`@After` wrap a single scenario. `@BeforeStep`/`@AfterStep` wrap each step inside that scenario. `@BeforeAll`/`@AfterAll` are `static`, take no arguments, and run once per JVM around the entire run — the place for a fixture that must not restart per scenario, such as the stub depot service a scaffolding-hire suite talks to. The failure half is what interviewers actually probe. When a step throws, the remaining steps are skipped and the scenario is reported failed, but the `@After` hooks still run; they run even when a `@Before` hook was what failed. `@AfterStep` runs after the step that failed, which is where a screenshot-on-failure hook belongs. That is why teardown and evidence capture live in hooks and never at the tail of the last step definition, which a failure never reaches.
code
java · 34 linesimport io.cucumber.java.After;
import io.cucumber.java.AfterAll;
import io.cucumber.java.AfterStep;
import io.cucumber.java.Before;
import io.cucumber.java.BeforeAll;
import io.cucumber.java.Scenario;
public class DepotHooks {
@BeforeAll
public static void startDepotStub() {
DepotStub.start();
}
@Before
public void resetHireLedger() {
DepotStub.resetLedger();
}
@AfterStep
public void recordStepOutcome(Scenario scenario) {
scenario.log("step finished: " + scenario.getStatus());
}
@After
public void releaseDepotHold() {
DepotStub.releaseHold();
}
@AfterAll
public static void stopDepotStub() {
DepotStub.stop();
}
}go deeper
Be ready to name the three pairs and say what each wraps: @Before/@After a scenario, @BeforeStep/@AfterStep a step, @BeforeAll/@AfterAll the whole run.
Explain the nesting and the failure rule: a failing step skips the rest of the steps but @After still runs, and the run-scope pair must be static and takes no arguments.
Show the cost reasoning — what belongs at run scope versus scenario scope, why an Examples row multiplies scenario hooks, and why cleanup at the end of the last step definition is a bug.
Own the convention: where teardown lives, what may be shared at run scope once parallel execution is on, and how a team keeps hook code from quietly becoming a second, invisible test framework.
## Three scopes, nested Cucumber-JVM's hooks are annotations in `io.cucumber.java`, placed on methods of any glue class the runner discovers. They come in three scopes that nest strictly inside each other: 1. **Run scope** — `@BeforeAll` / `@AfterAll`. Declared `public static`, no arguments. They fire once per JVM, before the first scenario and after the last one. A build that forks several JVMs runs them once per fork. 2. **Scenario scope** — `@Before` / `@After`. Instance methods, optionally taking a `Scenario` parameter. They fire around every scenario the run selects. 3. **Step scope** — `@BeforeStep` / `@AfterStep`. They fire around every step of the scenario that is currently executing. The execution shape for a run is therefore `@BeforeAll` → for each scenario ( `@Before` → for each step ( `@BeforeStep` → step → `@AfterStep` ) → `@After` ) → `@AfterAll`. Note what is *not* there: Cucumber-JVM has no feature-level hook. If setup must happen once per `.feature` file, you either lift it to run scope or make it idempotent at scenario scope. Other members of the family do have one, which is a common source of confusion when a team ports a suite. ## What "once per scenario" actually counts Each row of an `Examples` table is expanded into its own runnable scenario, so scenario-scope hooks fire once per row, not once per outline. In a scaffolding-hire depot suite, a returns outline with a 19-row `Examples` table fires `@Before` and `@After` 19 times. If that `@Before` opens a depot-database connection costing 1.4 seconds, the one outline pays 26.6 seconds of setup before a single assertion runs. Recognising that multiplier is usually what moves an expensive fixture from `@Before` up to `@BeforeAll`. ## Failure semantics — the half that is actually probed | event | what happens to the steps | `@AfterStep` | `@After` | `@AfterAll` | |---|---|---|---|---| | a step throws | the remaining steps are skipped, scenario is failed | runs after the failing step | runs | runs | | a `@Before` hook throws | every step is skipped, scenario is failed | — | runs | runs | | an `@After` hook throws | steps already finished | — | the scenario is failed | runs | | a whole scenario is filtered out by a tag expression | nothing is executed | — | — | runs | The rule to remember is that teardown is not conditional on success. `@After` is the only place where "release the depot hold no matter what happened" is actually guaranteed. Code written at the end of the last step definition is skipped by the very failures it is supposed to clean up after. ## Choosing a scope * **Expensive, shared, immutable** — starting a stub service, warming a container: run scope. Paying it 1,246 times instead of once is the single biggest avoidable cost in a large suite. * **Per-scenario isolation** — resetting the hire ledger, issuing a fresh auth token: scenario scope, because isolation is exactly what a scenario boundary means. * **Per-step evidence** — logging each step's outcome, capturing a screenshot at the moment of failure: step scope. Step hooks are also the only place that can act *between* two steps. * **Anything that must survive a failure** — `@After`, never the last step. Step hooks are the ones to use sparingly: they run on every step of every scenario, so a 40-millisecond screenshot in `@AfterStep` costs more than the steps themselves on a suite with eight steps per scenario. ## The same three scopes across the family | scope | Cucumber-JVM | cucumber-js | Behave | SpecFlow/Reqnroll | |---|---|---|---|---| | whole run | `@BeforeAll` / `@AfterAll` (static) | `BeforeAll` / `AfterAll` | `before_all` / `after_all` in `environment.py` | `[BeforeTestRun]` / `[AfterTestRun]` | | feature | — (no feature hook) | — | `before_feature` / `after_feature` | `[BeforeFeature]` / `[AfterFeature]` | | scenario | `@Before` / `@After` | `Before` / `After` | `before_scenario` / `after_scenario` | `[BeforeScenario]` / `[AfterScenario]` | | step | `@BeforeStep` / `@AfterStep` | `BeforeStep` / `AfterStep` | `before_step` / `after_step` | `[BeforeStep]` / `[AfterStep]` | Behave is the outlier in style: its hooks are named functions in `environment.py` rather than annotated methods discovered anywhere on the glue path, so there is exactly one of each and you branch inside it. ## Mistakes that show up in review * An instance `@BeforeAll` method — Cucumber-JVM requires the run-scope hooks to be static, and a non-static one is an error rather than a silently skipped method. * Cleanup written at the bottom of the last step definition, which never runs on the failing scenarios that need it most. * Scenario setup put in `@BeforeStep`, so it re-runs on every step of the scenario. * Treating `@BeforeAll` as "once per feature file" — it is once per JVM, and the run may interleave scenarios from many files.
- A @Before hook throws before any step runs — what does Cucumber-JVM do next?It skips the remaining `@Before` hooks and every step of that scenario, and reports the scenario as failed with the hook's exception. The `@After` hooks still run, so teardown and any evidence you attach there survive. The run continues with the next scenario; one failed hook does not abort the suite.
- Where would you put a fixture that takes 9 seconds to start — @BeforeAll or @Before?`@BeforeAll`, if the fixture can be shared safely, because it then costs 9 seconds per JVM instead of 9 seconds per scenario. Keep only the cheap reset that restores isolation in `@Before`. If the fixture cannot be shared — it holds per-scenario mutable state — the cost is the price of isolation and the fix is to make the fixture cheaper, not to move it.
- Cucumber-JVM has no feature-level hook. How do you handle setup that is genuinely per feature file?Either lift it to `@BeforeAll` and make it cover every file, or keep it at scenario scope and make it idempotent so repeating it is cheap. Teams sometimes fake a feature hook with a tagged `@Before` plus a guard, but that guard is shared mutable state and breaks under parallel execution.
saying these in an interview costs you the question
- Thinks @After is skipped when the scenario fails
- Puts teardown at the end of the last step definition
- Believes @BeforeAll runs once per feature file
- Uses @BeforeStep for setup that belongs at scenario scope
- Declares @BeforeAll as an instance method
- Assumes hooks fire once per Scenario Outline, not per row