In Cucumber, how do you make a @Before hook run only for scenarios carrying one tag, and when does that beat a Background?
answer
- the filter is an argument on the hook
- it takes an expression, not one name
- conditional, cross-file, invisible in the report
- reader's context stays in Gherkin
basics
~20 sGive the hook a tag expression: @Before("@depot-db") in Cucumber-JVM, Before({tags: '@depot-db'}, fn) in cucumber-js. Unlike a Background, that setup is conditional, reaches scenarios in every feature file, and stays out of the reader's scenario text.
solid answer
~50 sThe tag filter is an argument on the hook itself. In Cucumber-JVM it is the annotation's value, `@Before("@depot-db")`, and it accepts a full tag expression, so `@Before("@depot-db and not @readonly")` works too. In cucumber-js you pass `Before({tags: '@depot-db'}, function () { ... })`. SpecFlow/Reqnroll take the tag as an argument to the hook attribute; Behave has no per-tag decorator, so you branch on the scenario's tags inside `before_scenario` in `environment.py`. Against a `Background`, the tagged hook wins on three axes: it is **conditional**, it spans **every feature file** rather than one, and it stays **out of the report's step list**. That makes it the right home for technical setup — resetting a depot database, issuing a token — that a business reader gains nothing from seeing. Setup a reader genuinely needs in order to understand the scenarios stays in Gherkin.
code
java · 14 linesimport io.cucumber.java.Before;
public class DepotDatabaseHooks {
@Before("@depot-db")
public void seedScaffoldStock() {
DepotDb.seedStock(240);
}
@Before("@depot-db and not @readonly")
public void openWriteTransaction() {
DepotDb.beginWrite();
}
}go deeper
Know that a hook can be limited to tagged scenarios and roughly how it is written — the tag goes in the hook's own declaration, not in the feature file.
Explain that the argument is a full tag expression, and give the concrete difference from a Background: conditional, spans all files, and does not appear as steps in the report.
Show the judgement — plumbing in hooks, reader context in Gherkin — and be ready to price it: how many scenarios carry the tag, and what the setup costs each time.
Own the failure mode: tagged hooks are invisible dependencies. Decide whether the suite opts in or opts out, and how a newcomer discovers which tags carry behaviour.
## How the filter is spelled | implementation | tagged scenario hook | |---|---| | Cucumber-JVM | `@Before("@depot-db")` — the annotation's value is the tag expression | | cucumber-js | `Before({ tags: '@depot-db' }, function () { ... })` | | SpecFlow/Reqnroll | the tag is passed to the hook attribute, written without the leading `@` | | Behave | no per-tag decorator; branch on the running scenario's tags inside `before_scenario` in `environment.py` | Two details are worth being precise about. First, the argument is a **tag expression**, not a tag name, so the full boolean grammar applies: `@Before("@depot-db and not @readonly")` runs the hook only for scenarios that carry the first tag and not the second. Second, the run-scope hooks cannot be tagged at all — `@BeforeAll` fires before any scenario exists, so there are no tags to test. ## What the hook can do that a Background cannot | | tagged `@Before` hook | `Background` | |---|---|---| | written in | glue code | Gherkin, inside one `.feature` file | | applies to | every scenario carrying the tag, across all files | every scenario in that one file | | conditional | yes, via a tag expression | no | | visible in the report | only if it logs or attaches | yes, as steps | | failure surface | a hook failure on that scenario | a failed step | | right for | technical plumbing | context a reader needs | The decisive question is: **would a reader of the feature file be worse off not seeing this?** "Given the depot has 240 tubes in stock" is context; a reader needs it to make sense of what follows. "Truncate the hire ledger table and re-open a write transaction" is plumbing; putting it in the feature file makes the specification worse, not more honest. ## The cost argument, concretely A `Background` runs before every scenario below it in that file, and a scenario generated from an `Examples` row is one of those scenarios. In a scaffolding-hire depot suite, a returns feature with a 19-row `Examples` table and a four-step `Background` executes 76 background steps for one outline. If those steps drive the same depot reset that a hook could perform in a single call, the outline pays for the detour 19 times over. Moving the reset into `@Before("@depot-db")` removes the steps from the report as well as from the clock. The reverse cost is real too. Across a 1,246-scenario suite, 318 scenarios tagged `@depot-db` at 2.7 seconds of seeding each is 14.3 minutes of setup. That number is only visible because the tag makes the population explicit — which is itself an argument for the tagged hook over an unconditional one. ## The hidden-dependency cost — the senior half A tagged hook creates a dependency that is invisible from the scenario. A contributor writes a new scenario in the returns feature, forgets `@depot-db`, and gets a failure whose message is about a missing row rather than a missing tag. Ways to keep that honest: * **Prefer opt-out to opt-in when the majority needs the setup.** If 318 of 340 scenarios in a directory need the database, tag the 22 that do not and write `@Before("not @no-depot-db")`. * **Keep tags business-shaped where you can.** A tag that exists only to switch a hook on is a configuration flag wearing a label's clothes; a small number of them is fine, forty is a design problem. * **Fail loudly in the step, not silently in the hook.** If a step needs seeded data, let it assert that the data exists and say which tag provides it. * **Never let one hook's tag switch on state another scenario left behind** — that is a shared-state bug the tag merely hides. ## When the Background still wins Keep it in Gherkin when the setup is part of what the specification says. If every scenario in the file starts from "a depot with an open hire account", that sentence belongs in the file: it is the shared premise a reader needs, it appears in the generated documentation, and a failure in it reads as a failed step naming exactly which premise broke. A hook would hide all three. The heuristic that survives review: **Gherkin for what the reader needs, hooks for what the machine needs.**
- Can the filter on a hook use the whole tag-expression grammar, or only a single tag?The whole grammar. `and`, `or`, `not` and parentheses all work, so `@Before("@depot-db and not @readonly")` is valid, as is an opt-out form such as `@Before("not @no-depot-db")`. It is the same expression language the run-level filter uses, applied per hook instead of per run.
- A team tagged 318 scenarios @depot-db purely to trigger one hook. What would you change?Invert it. When the majority needs the setup, tag the minority that does not and write the hook as an opt-out, so a new scenario gets the right behaviour by default and only the exceptions have to remember a tag. The tag then also stops pretending to be a business label when it is really a switch.
- Can you tag a @BeforeAll hook so it only fires for part of the run?No. Run-scope hooks fire before any scenario exists, so there is nothing to match a tag expression against. If setup must vary by scenario population, it belongs at scenario scope, or the run itself is split by a tag expression into separate executions.
saying these in an interview costs you the question
- Thinks the hook argument accepts only one bare tag
- Puts technical database resets into a Background for visibility
- Tries to tag @BeforeAll or @AfterAll
- Adds a tag per hook until tags are pure configuration flags
- Assumes a Background can be made conditional on a tag