The behaviour is the spec — so write it once
Chenile treats a service’s behaviour as an executable specification written in Gherkin (Cucumber). A scenario reads like a description of what the service does:
Feature: Tests the s1 service using a REST client.
Scenario: op1 stamps the entity id
When I POST a REST request to URL "/s1/op1" with payload
"""
{ }
"""
Then the REST response key "id" is "S1ServiceImpl"
The important idea is that this .feature file describes behaviour, not mechanism. It never says how the request is sent — only what is sent and what must come back. That single property is what lets Chenile run the very same specification at two very different levels of the test pyramid.
I POST a REST request to URL … with payload, the REST response key … is …, the http status code is … — in two step libraries. Point your runner at one for a fast in-process unit test, or the other for a full integration test. The scenarios don't change.
Level 1 — unit / component tests over Spring MockMvc
For fast tests that run on every build, Chenile’s cucumber-utils (in chenile-core) implements the step vocabulary on top of Spring MockMvc. The service is stood up in an ephemeral, in-process instance — no network, no deployed server — and requests are driven from the outside.
Because the request enters through the real Chenile controller and pipeline, this is not a narrow unit test of one method: it exercises the service and its interceptors — the very policies from part 3. You are testing governance, not just logic.
The wiring is three small classes plus your feature files:
// 1) The runner — glue points at your steps + Chenile's REST steps
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = { "classpath:com/mycompany/app/bdd",
"classpath:org/chenile/cucumber/rest" }, // reusable steps
plugin = { "pretty" })
@ActiveProfiles("unittest")
public class CukesRestTest {}
// 2) Spring + MockMvc bootstrap (ephemeral instance)
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpringTestConfig.class)
@AutoConfigureMockMvc
@CucumberContextConfiguration
@ActiveProfiles("unittest")
public class CukesSteps {
@Given("dummy") public void dummy() {} // add service-specific steps here
}
// 3) Test configuration — scans Chenile + your service beans
@Configuration
@SpringBootApplication(scanBasePackages = {
"org.chenile.configuration", "com.mycompany.app.configuration" })
@ActiveProfiles("unittest")
public class SpringTestConfig extends SpringBootServletInitializer {}
Most of the steps you need already live in org.chenile.cucumber.rest.RestCukesSteps, so CukesSteps is usually just a hook for a handful of service-specific additions.
Level 2 — integration tests over REST Assured
When you want to verify a really deployed service — the wire protocol, the packaging, the environment and config — Chenile’s it-cucumber-utils (in the chenile-bdd repository) implements the same step vocabulary on top of REST Assured. Instead of a MockMvc call, I POST a REST request to URL … now issues a real HTTP request against a running server:
// it-cucumber-utils — same @When/@Then text, different engine
RestAssured.baseURI = targetHost;
RestAssured.port = targetPort;
@When("I POST a REST request to URL {string} with payload")
// ... builds the request with REST Assured's given()/when()/then()
Point the integration runner’s glue at org.chenile.cucumber.rest from it-cucumber-utils, deploy the service (locally, in a container, or to staging), and your existing scenarios now validate the live system end to end. chenile-bdd also ships it-cucumber-sec-utils for the same treatment of secured endpoints.
Why this matters
✅ What you gain
- No duplicated test logic. One
.featureis your unit and integration spec. - Governance is tested. Interceptors/policies run in the MockMvc path, so security, tenancy and i18n are covered.
- Confidence at both speeds. Fast in-process tests on every build; full REST Assured tests before release.
- Living documentation. Gherkin scenarios read as behaviour anyone can review.
🔁 What actually changes between levels
- The runner and its glue package
- The step library:
cucumber-utils↔it-cucumber-utils - The target: ephemeral MockMvc ↔ a deployed URL
- Not the scenarios. Swap the glue, not the spec.
This is the same philosophy that runs through the rest of Chenile: write a thing once, decide where it runs by configuration. Policies run at the gateway or the last mile; a Gherkin spec runs as a unit test or an integration test. The behaviour is defined in exactly one place.
org.chenile.cucumber.rest.RestCukesSteps (MockMvc, in cucumber-utils) and the REST Assured equivalent in chenile-bdd/it-cucumber-utils. Working samples live in the chenile-samples projects chenile-bdd-sample and service-with-persistence.