SpecFormula AI
ISABackendInstructions

EntitySetup

Prepare test data with declarative syntax

EntitySetup lets you declare test precondition data directly in Gherkin, without writing INSERT statements in Step Definitions.

Basic Usage

Given prepare a user, with table:
  | >Alice.id | name  | email             | status |
  | <userId   | Alice | alice@example.com | ACTIVE |

This instruction will:

  1. Map user to the users table via Entity Spec and retrieve column definitions
  2. Insert a record with name = Alice, email = alice@example.com, status = ACTIVE into the database
  3. Query back the user_id column value from database, store in variable Alice.id

Variable Extraction

After INSERT, use > and < pairs to query back auto-generated column values (e.g., auto-increment ID) from the database and store them in variables for subsequent steps:

  • >variableName (header row): Defines the variable name to store the extracted value
  • <columnName (data row): Specifies the database column name to query back
Given prepare a user, with table:
  | >Alice.id | >Alice.createdAt | name  | email             | status |
  | <userId   | <createdAt       | Alice | alice@example.com | ACTIVE |

Execution flow:

  1. Write name, email, status to the database
  2. Query back userId column value from database → store in variable Alice.id
  3. Query back createdAt column value from database → store in variable Alice.createdAt

Variable Reference

Use $variable to reference previously stored variables:

Given prepare a user, with table:
  | >Alice.id | name  |
  | <userId   | Alice |

Given prepare a todo, with table:
  | userId    | title    |
  | $Alice.id | Buy milk |

JSON Field Description

When Entity fields store JSON objects (e.g., PostgreSQL JSONB, MySQL JSON), two methods are available to describe JSON field content.

JSON Path DataTable Header

Use path syntax in DataTable headers to expand JSON structures:

Given prepare an order, with table:
  | id | orderItems[0].productId | orderItems[0].quantity | orderItems[1].productId | orderItems[1].quantity |
  | 1  | 5                       | 2                      | 6                       | 10                     |

Equivalent to the compressed single-line string:

Given prepare an order, with table:
  | id | orderItems                                                   |
  | 1  | [{"productId":5,"quantity":2},{"productId":6,"quantity":10}] |

With Symbol System:

Given prepare an order, with table:
  | id | orderItems[0].productId | orderItems[0].createdAt |
  | 1  | $productId              | @time(now)              |

JSON DocString Format

Use with json: with """json ... """ to write structured JSON directly, ideal for deeply nested structures:

Given prepare an order, with json:
  """json
  {
    "id": 1,
    "orderItems": [
      { "productId": 5, "quantity": 2 },
      { "productId": 6, "quantity": 10 }
    ]
  }
  """

Multiple records using JSON Array:

Given prepare an order detail, with json:
  """json
  [
    { "orderId": 1, "productId": 101, "quantity": 2 },
    { "orderId": 1, "productId": 102, "quantity": 1 }
  ]
  """

With Symbol System:

Given prepare an order, with json:
  """json
  {
    >orderId: <id,
    "orderNo": "ORD-001",
    "createdAt": @time(now),
    "orderItems": [
      { "productId": $productId, "quantity": 2 }
    ]
  }
  """

JSON format note: Symbol system expressions ($ variables, & CAS constraints, @ time symbols, >/< extraction keys) must NOT be placed inside JSON string quotes "", otherwise they are treated as plain strings.

Escaping Keys Containing .

When a JSON key itself contains a . character, use ["..."] bracket notation to prevent it from being split into nested levels:

Given prepare a setting, with table:
  | id | content.["btn.save"] | content.["btn.cancel"] |
  | 1  | Save                 | Cancel                 |

In JSON DocString, use the original key directly — the framework handles escaping automatically.


Mapping to Entity Spec

The EntitySetup instruction maps business entity names to actual database tables through Entity Spec, and validates fields against the DDL.

Mapping Mechanism

# entity_to_table_mapping.yml
entity_to_table_mapping:
  - user: users
  - todo: todos
  - order: orders
  - product: products
-- schema.sql
CREATE TABLE users (
    user_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    status VARCHAR(20) DEFAULT 'ACTIVE',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Given prepare a user, with table:
  | name  | email             |
  | Alice | alice@example.com |

# SpecFormula automatically maps to users table
# INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
Gherkin ElementMapping Source
userentity_to_table_mapping key
users tableentity_to_table_mapping value
name, email fieldsDDL column definition

Naming Conventions

Entity names: Use Chinese or English names familiar to the business team

# ✅ Recommended: business language
entity_to_table_mapping:
  - member: members
  - order: orders
  - product: products

# ❌ Avoid: technical names
entity_to_table_mapping:
  - member_entity: members
  - tbl_order: orders

Field name conversion: DataTable uses camelCase, automatically mapped to database snake_case

Gherkin: userId    → DB: user_id
Gherkin: createdAt → DB: created_at

When resolving or validating database columns, the framework applies the same three-step strategy for each field name:

  • Step 1: exact match
    • First, it looks for the original field name as-is, for example userId vs userId.
  • Step 2: camelCase → snake_case
    • If no exact match is found, it converts userId to user_id and tries to match again against the DDL / actual data.
  • Step 3: case-insensitive
    • If the first two steps still fail, it finally falls back to case-insensitive comparison, so user_id, USER_ID, and User_Id are treated as the same column.
Feature field nameDDL / Map field nameMatch?Explanation
userIduserId✅ YesStep 1: direct equals match
userIduser_id✅ YesStep 2: camelCase → snake_case
userIdUSER_ID✅ YesStep 2 to user_id, then Step 3 equalsIgnoreCase

In practice, as long as you consistently use camelCase in Specs, the engine can robustly resolve both snake_case and upper-case column names from the DDL, without requiring manual handling of case or underscores in SQL or code.

Schema Validation

SpecFormula validates that fields in DataTable exist against the DDL:

# ✅ Correct: fields exist in DDL
Given prepare a user, with table:
  | name  | email             | status |
  | Alice | alice@example.com | ACTIVE |

# ❌ Lint error: field does not exist
Given prepare a user, with table:
  | name  | emailAddress      |    # emailAddress doesn't exist, should be email
  | Alice | alice@example.com |

Why Not Use API to Create Precondition Data?

Creating precondition data through API causes test coupling—if the "create user" API has a bug, all tests depending on that user will fail, even if the feature being tested is completely correct.

# ❌ Using API: create user API bug affects all tests
When (No Actor) create user, call table:
  | name  | email             |
  | Alice | alice@example.com |

# ✅ EntitySetup: direct database insert, unaffected by API
Given prepare a user, with table:
  | name  | email             |
  | Alice | alice@example.com |

Type Conversion

Values in DataTable are all strings. The framework auto-converts based on the column type defined in DDL (e.g., INT, DATE, DATETIME):

DDL Column TypeDataTable InputConversion Result
INT / BIGINT"123"Integer / Long
DECIMAL"99.99"BigDecimal
DATE"2026-01-27"LocalDate
DATETIME / TIMESTAMP"2026-01-27T10:00:00"LocalDateTime
DATETIME / TIMESTAMP@time("now")Current Mock time
DATE@date("now")Current Mock date

Error Handling

Lint Phase

ErrorDescription
Entity not definedEntity not found in entity_to_table_mapping
Field not foundField in DataTable not found in table definition

Runtime

ErrorDescription
INSERT failedDatabase constraint violation (unique key conflict, NOT NULL, etc.)

Complete Example

Feature: Create Todo

  Background:
    Given current time is "2026-01-27T10:00:00"

    # EntitySetup instruction — prepare user data
    # >Alice.id / <userId: query back userId after INSERT, store in Alice.id
    Given prepare a user, with table:
      | >Alice.id | name  | email             | passwordHash | status |
      | <userId   | Alice | alice@example.com | password123  | ACTIVE |

  Example: Create a todo item

    # ApiCall instruction (see ApiCall docs)
    When (UID="$Alice.id") create todo, call table:
      | >todo1.id | title    |
      | <todoId   | Buy milk |

    # ResponseValidate instruction (see ResponseValidate docs)
    Then create todo(201) response, with table:
      | todoId    | userId    | title    |
      | $todo1.id | $Alice.id | Buy milk |

On this page