Skip to main content
Version: 2.29

Blueprints

A Blueprint is a YAML file that declares the data a test needs before it runs. It names the records to set up (such as orders, inventory, customers, and parts) and it describes where each value comes from. A Blueprint answers one question: what data should exist?

A Blueprint does not describe the test

Declare the data a test needs, not what the test does. Describing behavior remains the job of a Feature File.

Blueprints are the authoring format for Intelligent Data, the Cycle Intelligence capability that sets up and cleans up test data. Cycle runs a Blueprint through an engine compiled into cycle-cli, so there is no extra service or server to stand up. A Blueprint is invoked the way a Feature File is.

Why use Blueprints?

Test data is the most common reason a passing test starts failing. A test that depends on an order somebody created by hand last quarter breaks the moment that order ships, gets archived, or is claimed by another tester. Blueprints replace that shared, aging data with data the test creates for itself.

  • Fresh data on every run: The Blueprint creates what the test needs at the moment the test starts, so results do not depend on what is left over in the environment.
  • No hardcoded values: Every value the Blueprint produces is published back to the test as a variable, so the test refers to $blueprint-order-ordnum instead of a literal order number.
  • Automatic cleanup: A teardown step removes what the run created, in reverse order, which keeps environments from filling up with test records.
  • One format for every system: A Blueprint for a MOCA-based warehouse management system reads like a Blueprint for a REST API, a relational database, or MongoDB.
  • Reuse across tests: A Blueprint is a standalone file, so many Feature Files can request the same data state without duplicating setup steps.
  • Reviewable like code: A Blueprint is plain YAML in source control, so a data change goes through the same review as a code change.
What about datasets?

Both datasets and blueprints can load and clean up data. However, blueprints provide a more declarative format that can handle comprehensive data operations across multiple sources. Blueprints are also the authoring format for Intelligent Data, so an AI coding agent can help write, validate, and run them.

How Blueprints work

A Blueprint declares one or more entities. An entity is one logical record, such as a database row, an API object, a document, or a row in a file.

For each entity, Cycle works through the same lifecycle:

  1. Decide where each value comes from: A value can be one you supply, one Cycle generates, one it looks up in the target system, or one an earlier record produced.
  2. Put the record in place: Depending on what you asked for, Cycle creates the record, finds one that already fits, or confirms that a suitable one exists.
  3. Check the result: Optional checks confirm the system reached the state you expected. A failed check stops the run.
  4. Hand the values to the test: Every value the Blueprint produces becomes a variable the rest of the test can read. The Variables and Placeholders page lists the exact names.

What you need to get started

A Blueprint needs somewhere to create the data, so the only hard requirement is a way to reach the target system. That can be a connection already stored in your project, or one the Blueprint declares itself.

You do not need to set up a library, a catalog, or a system configuration to write your first Blueprint. Those come into play once a Blueprint names a system_config, runs by catalog ID, or is shared across a team, and the section on where Blueprints live covers them when you are ready. A single self-contained file is a perfectly good starting point.

What a Blueprint looks like

This Blueprint creates one customer row with a generated key, confirms the row exists, and removes it at teardown. It uses a connection named crmdb, which is a connection stored in the project.

name: create-customer
description: >
One customer row with a generated, unique customer number.

defaults:
connection: crmdb

entities:
- name: customer
action:
create:
sql: { command: "insert into customers (custnum, name) values ('{custnum}', '{name}')" }
fields:
- name: custnum
source:
generate:
template: { template: "CUST-{seq}", start: 1, pad: 4 }
- name: name
source: { const: { value: "Blueprint Test Customer" } }
verify:
- sql: { command: "select custnum from customers where custnum = '{custnum}'", expect_rows: 1 }
teardown:
- sql: { command: "delete from customers where custnum = '{custnum}'" }

Reading it from the top: the entity is named customer, and its action is to create a row with a SQL insert. Its two fields supply the values that insert needs. In the custnum field, {seq} is a counter Cycle increments on each run, and pad: 4 widens it, so you get CUST-0001 rather than CUST-1. The verify block confirms exactly one matching row exists. The teardown block removes it when you are finished.

Running it produces a customer number such as CUST-0001 and publishes it as the variable blueprint-customer-custnum.

Using a Blueprint in a Feature File

Two steps in the Blueprint step category run a Blueprint from CycleScript. They use the same engine as every other way of running a Blueprint, so behavior does not change between a test run and a manual run.

Feature: order fulfillment

Scenario: fulfill an order the Blueprint created
Given I create data using blueprint "blueprints/create-order.yml"
Then I verify text $blueprint-order-ordnum is equal to "ORD-0001"
Given I teardown data using blueprint "blueprints/create-order.yml"

The I create data using blueprint "<path>" step runs the Blueprint and publishes its declared field values into the Scenario. Those variables come into scope only when the step passes. A failed run still reports the values it realized and records them for teardown, but later steps cannot read them.

The I teardown data using blueprint "<path>" step reverses the run. Teardown never fails a Scenario, so a cleanup problem cannot hide a real test result.

A Scenario can also pin a specific value. Any variable in scope named {entity}-{field}, or the bare {field}, replaces the matching Blueprint field, which lets one Blueprint serve several tests without editing the file.

Ensure data stays in place

An entity that uses the ensure action may find a record that already existed, so Cycle never removes it during teardown. Use ensure for data that should persist, such as a warehouse or a client record.

Where Blueprints live

A blueprint library is a folder of plain files with no database and no server behind it. It is what a Blueprint resolves a system_config against, what a run by catalog ID reads, and how a team shares Blueprints. A library is organized by system, and each system carries a system configuration that describes how to reach it.

{libraryDir}/
blueprint-catalog.yml # index of every scenario in the library
project.yml # authoring defaults for the library
{system}/
system-config.yml # that system's connections, default values, and components
blueprints/ *.yml # the Blueprints themselves
lookups/ *.yml # reference data used while authoring
create-specs/*.yml # create templates and conventions for the system

The catalog at the library root indexes every scenario with its identifier, description, tags, and status. A scenario marked as a stub is one that has been catalogued but not yet authored.

A project points at its library through the blueprint.libraryDir setting in the .cycproj file.

{
"settings": { "blueprint.libraryDir": "C:/path/to/blueprint-library" }
}

A second setting, blueprint.sharedLibraryDir, adds a fallback library underneath the project's own. Cycle searches blueprint.libraryDir first and blueprint.sharedLibraryDir second, and the first match wins. A project's own copy of a system configuration therefore overrides the shared one, which lets a team publish a common set of system configurations while a project overrides only the ones it needs to change.

{
"settings": {
"blueprint.libraryDir": "C:/path/to/project-library",
"blueprint.sharedLibraryDir": "//server/share/blueprint-library"
}
}
A shared library supplies system configurations only

blueprint.sharedLibraryDir is read when a Blueprint resolves its system_config, and nowhere else. The catalog and a run by catalog ID read blueprint.libraryDir alone, so a scenario that exists only in the shared library is not addressable by its identifier.