In Cucumber-JVM with Spring, what does @CucumberContextConfiguration mark and why may only one class carry it?
answer
- Spring needs one configuration entry point
- One glue class, carrying Spring test annotations
- A second one fails the run
- Glue classes become injectable Spring beans
- Context cached; glue rebuilt per scenario
basics
~20 s@CucumberContextConfiguration marks the single glue class that tells cucumber-spring how to bootstrap the Spring test context. Exactly one entry point is allowed, so a second annotated class fails the run at startup. Step definition classes then become injectable Spring beans.
solid answer
~50 s`@CucumberContextConfiguration` comes from `cucumber-spring` and goes on **one** glue class, alongside the Spring test annotation that actually describes the context — typically `@SpringBootTest` or `@ContextConfiguration`. It marks that class as the entry point cucumber-spring uses to build the Spring `TestContext` for the run. Only one class may carry it because Cucumber has no rule for choosing between two candidate configurations; if it finds a second in the glue path it aborts at startup and names both classes. What the step definition classes get in return is membership of the Spring container: they are instantiated as beans, so constructor injection and `@Autowired` work, and a scenario can inject the same repository, client or scheduling service the application itself uses. Spring caches the application context across scenarios, while glue beans are recreated per scenario in cucumber-spring's `cucumber-glue` scope.
code
java · 23 linesimport io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
// Exactly one class in the glue path carries this
@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CucumberSpringConfiguration {
}
// Any step definition class is now a Spring bean
public class DocketSteps {
private final HearingScheduler scheduler;
public DocketSteps(HearingScheduler scheduler) {
this.scheduler = scheduler;
}
@Given("case {string} is booked into courtroom 4B")
public void caseIsBooked(String caseNumber) {
scheduler.book(caseNumber, "4B");
}
}go deeper
Recall that one empty class in a Cucumber-JVM Spring suite carries this annotation next to the Spring test annotation, and that its presence is what makes step definition classes injectable.
Explain the mechanics: it names the single class Cucumber uses to build the Spring test context, a second one aborts the run, and the glue classes become beans so constructor injection works.
Demonstrate the lifetime judgement: the application context is cached across scenarios while glue objects are not, so a mutable singleton silently shares state. Be ready to describe how you keep per-scenario state out of it.
Own the tradeoff between context caching and isolation across a large suite: how many distinct Spring configurations a test estate can afford, when profiles beat a second context, and what resetting strategy teams follow at scenario boundaries.
## What the annotation marks `cucumber-spring` is the object factory that hands Cucumber-JVM's glue classes to Spring instead of instantiating them directly. For that to work, Spring's TestContext framework needs to know **which configuration describes the application context** — the same question `@SpringBootTest` or `@ContextConfiguration` answers on an ordinary Spring test class. But Cucumber has no test class: it has a glue path full of step definition classes, none of them privileged. `@CucumberContextConfiguration` is how you privilege exactly one of them. You put it on a class in the glue path, together with the Spring annotation that describes the context: ```java import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.context.SpringBootTest; @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class CucumberSpringConfiguration { } ``` The class is usually empty. Its whole job is to carry annotations. It must nonetheless sit **inside the glue path**, because that is where Cucumber looks; a configuration class in a package the runner never scans is the most common reason a suite reports that no context configuration was found. ## Why exactly one Cucumber builds one Spring `TestContextManager` for the run from the annotated class. Two annotated classes would describe two different contexts, and nothing in the model says which one the step definitions belong to — a scenario whose steps span both would be incoherent. Rather than guess, Cucumber **fails at startup** and reports that more than one glue class carries the annotation, naming them. That is worth knowing as a diagnosis, because the failure has a mundane cause: a suite grows a second configuration class when a team adds a "web" configuration next to an existing "service" one. The fix is not a second annotation but a single configuration class whose Spring annotations cover both needs, or profiles selected through the Spring annotations on that one class. ## What the step definition classes get Once cucumber-spring is in charge, glue classes stop being plain objects: - They are **created as Spring beans**, so a constructor parameter of any application type is injected, and `@Autowired` works on fields. - They can inject **the real collaborators of the application under test** — a repository, a scheduling service, a `TestRestTemplate` bound to the random port. - They share those beans with each other, which is the state-sharing answer: two step definition classes injecting the same singleton bean see the same object, and two classes injecting the same scenario-scoped bean see the same per-scenario object. - They can use Spring test facilities the same way an ordinary test class would, including transactional behaviour and property overrides declared on the configuration class. ## Context caching versus scenario scope This is the part that separates a candidate who has run a Spring suite from one who has read about it. | Thing | Lifetime | | --- | --- | | The Spring application context | Cached and reused across scenarios by Spring's TestContext framework | | Singleton beans inside that context | As long as the context — the whole run, in practice | | Glue (step definition) instances | Recreated for every scenario | | Beans in cucumber-spring's `cucumber-glue` scope | Recreated for every scenario, discarded afterwards | Context caching is a performance decision and a good one — starting a Spring Boot context per scenario would make a courtroom-scheduling suite unrunnable inside an eleven-minute pipeline budget. But it has a consequence people miss: **a mutable singleton bean is shared by every scenario in the run.** If a step definition writes the current case number onto an application-scoped bean, the isolation you thought DI gave you is gone, and the symptom is exactly the same order-dependent flakiness a static field produces. The rule that follows: 1. Mutable per-scenario state goes in a scenario-scoped bean or in a plain object created per scenario — not in a singleton. 2. Singletons in the test configuration are for **immutable** things: clients, configuration, fixtures that never change. 3. If you must mutate application state (a seeded database, a stubbed clock), reset it explicitly at a scenario boundary rather than hoping the context restart will do it, because there is no restart. ## Failure modes to recognise - **"No context configuration"** — the annotated class is not in the glue path. - **Two annotated classes** — the run refuses to start; consolidate into one. - **Fields null in a step definition** — cucumber-spring is not on the test classpath, so Cucumber is still instantiating glue classes itself and Spring never sees them. - **A scenario passing alone and failing in the suite** — almost always mutable state on a cached singleton. One more practical constraint: cucumber-spring is an object factory, and only one object factory may be active. Leaving `cucumber-picocontainer` on the classpath beside it gives Cucumber two candidates and it will not choose for you.
- If the Spring context is cached across scenarios, what stops one scenario's state leaking through a singleton bean?Nothing automatic. A singleton lives as long as the cached context, so mutable state written onto it survives into the next scenario and produces order-dependent failures. Keep per-scenario state in a scenario-scoped bean or a plain object built per scenario, keep singletons immutable, and reset any external state you deliberately mutate at a scenario boundary.
- What happens if both cucumber-spring and cucumber-picocontainer are on the test classpath?Cucumber finds two object factories and refuses to pick one, so the run fails before any scenario executes. Either remove the dependency you do not want, or name the factory explicitly through Cucumber's `cucumber.object-factory` configuration option. Keeping both so that some classes use Pico and others use Spring is not a supported arrangement.
- Where must the annotated class live for Cucumber to find it?Inside the glue path the runner scans. A configuration class sitting in a package outside the glue packages is invisible, and the run fails reporting that no context configuration was found. Teams usually put it in the root glue package precisely so that any glue package configuration includes it.
saying these in an interview costs you the question
- Puts the annotation on every step definition class
- Thinks a fresh Spring context starts per scenario
- Expects autowiring without cucumber-spring on the classpath
- Believes it replaces @SpringBootTest rather than accompanying it
- Keeps mutable scenario state on a singleton bean
- Leaves the configuration class outside the glue path