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:

  1. Behavior โ€” what the user sees, with examples and edge cases.
  2. Driven โ€” Given / When / Then scenarios that lead the work (this page).
  3. 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:

TEXT
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:

TEXT
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:

ScenarioTest
hashtags only while they fittest_text_adds_hashtags_only_while_they_fit()
long text is cut to 300test_long_text_is_cut_to_300()
numbers and URL fragments are not tagstest_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:

TEXT
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:

TEXT
test_text()
test_adapter_works()
test_bluesky_1()

Names that describe behavior are readable by anyone:

TEXT
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:

TEXT
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.


Next