Architecture#
This page provides an overview of tomato's internal architecture and how components interact during test execution.
High-Level Overview#
tomato is designed around three core concepts:
- Containers - Docker containers managed via Testcontainers
- Resources - Abstractions over external services (databases, APIs, message queues)
- Handlers - Step definitions that interact with resources
flowchart TB
subgraph Config["tomato.yml"]
C[Containers]
R[Resources]
F[Features]
end
subgraph Runtime["tomato Runtime"]
TC[Testcontainers]
H[Handlers]
G[Gherkin Parser]
end
subgraph External["External Services"]
DB[(PostgreSQL)]
REDIS[(Redis)]
KAFKA[(Kafka)]
HTTP[HTTP Server]
WS[WebSocket]
end
C --> TC
R --> H
F --> G
TC --> DB
TC --> REDIS
TC --> KAFKA
H --> DB
H --> REDIS
H --> KAFKA
H --> HTTP
H --> WS
G --> H
Test Execution Flow#
When you run tomato run, the following sequence occurs:
sequenceDiagram
participant User
participant CLI
participant Config
participant TC as Testcontainers
participant App as Application
participant Handler
participant Godog
User->>CLI: tomato run
CLI->>Config: Parse tomato.yml
rect rgb(40, 40, 40)
Note over TC: Container Setup
Config->>TC: Start containers
TC-->>TC: Wait for readiness
TC-->>Config: Container endpoints
end
rect rgb(40, 40, 40)
Note over App: App Setup (optional)
Config->>App: Start application
App-->>App: Wait for ready check
end
rect rgb(40, 40, 40)
Note over Handler: Resource Setup
Config->>Handler: Initialize handlers
Handler-->>Handler: Connect to services
end
rect rgb(40, 40, 40)
Note over Godog: Test Execution
CLI->>Godog: Run features
loop Each Scenario
Godog->>Handler: Reset resources
loop Each Step
Godog->>Handler: Execute step
Handler-->>Godog: Result
end
end
end
Godog-->>CLI: Test results
CLI->>TC: Stop containers
CLI->>App: Stop application
CLI-->>User: Exit code
Handler Architecture#
Handlers are responsible for translating Gherkin steps into actions against resources:
flowchart LR
subgraph Steps["Gherkin Steps"]
S1["Given I set 'db' table..."]
S2["When I send 'GET' request..."]
S3["Then response status is '200'"]
end
subgraph Handlers["Handler Registry"]
PG[PostgreSQL Handler]
HTTP[HTTP Client Handler]
REDIS[Redis Handler]
KAFKA[Kafka Handler]
WS[WebSocket Handler]
SHELL[Shell Handler]
end
subgraph Resources["Resources"]
DB[(Database)]
API[HTTP API]
CACHE[(Cache)]
MQ[(Message Queue)]
WSS[WebSocket Server]
CMD[Shell]
end
S1 --> PG
S2 --> HTTP
S3 --> HTTP
PG --> DB
HTTP --> API
REDIS --> CACHE
KAFKA --> MQ
WS --> WSS
SHELL --> CMD
Resource Lifecycle#
Each resource follows a consistent lifecycle:
stateDiagram-v2
[*] --> Configured: Parse config
Configured --> Initialized: Initialize handler
Initialized --> Connected: Connect to service
Connected --> Ready: Ready for tests
Ready --> Reset: Before scenario
Reset --> Ready: Clean state
Ready --> Closed: Tests complete
Closed --> [*]
Container Orchestration#
tomato uses Testcontainers to manage Docker containers:
flowchart TB
subgraph Config["Configuration"]
YAML["tomato.yml"]
end
subgraph Testcontainers["Testcontainers Runtime"]
CM[Container Manager]
NW[Network]
subgraph Containers
C1[postgres:15]
C2[redis:7]
C3[kafka:latest]
end
end
subgraph WaitStrategies["Wait Strategies"]
PORT[Port Check]
HTTP_CHECK[HTTP Check]
LOG[Log Pattern]
end
YAML --> CM
CM --> NW
CM --> C1
CM --> C2
CM --> C3
C1 --> PORT
C2 --> PORT
C3 --> LOG
Data Flow Example#
Here's how a typical database test scenario flows through the system:
sequenceDiagram
participant Feature as Feature File
participant Godog
participant PG as PostgreSQL Handler
participant DB as PostgreSQL
Note over Feature: Scenario: Create user
Feature->>Godog: Given I set "db" table "users"...
Godog->>PG: SetTableValues(table, data)
PG->>DB: INSERT INTO users...
DB-->>PG: OK
PG-->>Godog: Success
Feature->>Godog: When I execute on "db" query...
Godog->>PG: ExecuteQuery(query)
PG->>DB: SELECT * FROM users
DB-->>PG: Results
PG-->>Godog: Store results
Feature->>Godog: Then "db" query result should be...
Godog->>PG: AssertQueryResult(expected)
PG-->>Godog: Match/No match
HTTP Request/Response Flow#
sequenceDiagram
participant Feature as Feature File
participant HTTP as HTTP Handler
participant API as Target API
Feature->>HTTP: Given I set request header...
HTTP-->>HTTP: Store header
Feature->>HTTP: When I send "POST" to "/users"
HTTP->>API: POST /users
API-->>HTTP: 201 Created + JSON body
HTTP-->>HTTP: Store response
Feature->>HTTP: Then response status is "201"
HTTP-->>HTTP: Assert status
Feature->>HTTP: And response JSON "id" exists
HTTP-->>HTTP: Assert JSON path
Configuration Structure#
flowchart TB
subgraph tomato.yml
direction TB
V[version: 2]
subgraph containers
C1[name: postgres<br/>image: postgres:15<br/>env: ...<br/>wait_for: ...]
C2[name: redis<br/>image: redis:7]
end
subgraph resources
R1[name: db<br/>type: postgres<br/>container: postgres]
R2[name: cache<br/>type: redis<br/>container: redis]
R3[name: api<br/>type: http<br/>base_url: ...]
end
subgraph app
A[command: go run ./cmd/server<br/>port: 8080<br/>ready: http /health<br/>env: ...]
end
subgraph features
F[paths:<br/> - ./features]
end
end
Error Handling#
When a step fails, tomato provides detailed error information:
flowchart TB
Step[Step Execution] --> Check{Success?}
Check -->|Yes| Next[Next Step]
Check -->|No| Error[Error Details]
Error --> Type{Error Type}
Type -->|Connection| Conn[Connection failed to resource]
Type -->|Assertion| Assert[Expected vs Actual mismatch]
Type -->|Timeout| Timeout[Operation timed out]
Type -->|Syntax| Syntax[Invalid step syntax]
Conn --> Report[Report to User]
Assert --> Report
Timeout --> Report
Syntax --> Report
Report --> Cleanup[Cleanup Resources]
Cleanup --> Exit[Exit with error code]