Given / When / Then in WordPress: Scenarios That Drive the Code and the AI
In BDD the scenarios drive the work. Not the framework, not the database schema, not the AI agent’s first idea. You write what must happen, and everything else follows.
This is the second of three BDD principles on 4wp.dev:
- Behavior โ what the user sees, with examples and edge cases.
- Driven โ Given / When / Then scenarios that lead the work (this page).
- Development โ scenario โ test โ code โ green.
๐ Overview: Behavior-Driven Development for WordPress
The format
A scenario has three parts:
- Given โ the starting state.
- When โ the one action.
- Then โ the result the user sees.
The format became widely known through Cucumber and its Gherkin syntax. You don’t need either tool to use it: it is a way to write, not a file type.
From our real case, 4WP Social posting to Bluesky:
Scenario: hashtags only while they fit
Given description "Demo #WordPress"
And tags "WordPress", "FAQ schema"
When 4WP Social builds the Bluesky text
Then the text is "Demo #WordPress\n\n#FAQschema"
One scenario, one behavior
A scenario that checks three things fails for three reasons, and you don’t know which one. Keep one behavior per scenario, and write the edge cases as their own scenarios:
Scenario: long text is cut to 300
Given a description of 400 characters
When 4WP Social builds the Bluesky text
Then the text has 300 characters
And it ends with "โฆ"
Scenario: numbers and URL fragments are not tags
Given the text "Issue #123 and a#b"
When 4WP Social finds tags for Bluesky
Then there are no tags
Each When has exactly one action. If you need two “When” lines, you have two scenarios.
One language: the test name is the spec
The scenario name moves straight into the test name. From tests/unit/BlueskyTest.php in 4WP Social:
| Scenario | Test |
|---|---|
| hashtags only while they fit | test_text_adds_hashtags_only_while_they_fit() |
| long text is cut to 300 | test_long_text_is_cut_to_300() |
| numbers and URL fragments are not tags | test_numbers_and_url_fragments_are_not_tags() |
The whole plugin follows this rule. Read a few more test names from the same suite, and you get the product specification without opening a single class:
test_tags_are_cleaned_and_limited_to_four()
test_caption_adds_missing_hashtags_and_respects_limit()
test_privacy_falls_back_to_self_only_for_unaudited_apps()
test_borrowed_item_takes_media_but_not_the_copy()
test_empty_result_does_not_erase_known_url()
test_calendar_uses_site_time_zone()
The client, the developer and QA use the same words. When a client says “the calendar shows the wrong day”, there is already a test called test_calendar_uses_site_time_zone(). That is the same idea as Ubiquitous Language in Domain-Driven Design: one language for the business and the code.
๐ Ubiquitous Language in WordPress Development
Bad and good scenario names
Names that describe the code tell nothing about behavior:
test_text()
test_adapter_works()
test_bluesky_1()
Names that describe behavior are readable by anyone:
test_long_text_is_cut_to_300()
test_numbers_and_url_fragments_are_not_tags()
A simple check: read the test name aloud to the client. If they understand it and can say “yes, that’s right” or “no, that’s wrong”, it is a good name.
It drives the AI
A scenario is the clearest prompt you can give a coding agent. The behavior is fixed; the implementation is up to the agent:
Make BlueskyTest pass.
Do not change the scenarios.
Compare with a prompt without scenarios: “add hashtags to Bluesky posts”. The agent will decide the limit, the duplicates and the edge cases itself, and you will find out which decisions it made only when a user complains.
With scenarios:
- the agent has a precise goal โ the tests;
- every change it makes is checked by a run;
- the scenarios stay yours, even when the code is rewritten.
AI writes the code. You define the behavior.
Where scenarios live
In 4WP Social the scenarios live in the test names and the test bodies. There are no .feature files. For a small team that is enough: the tests are the single place where the behavior is written.
Separate .feature files and tools like Behat or Cucumber make sense when non-developers edit scenarios themselves. The format is the same; only the place changes.