← Testing

TESTING FEATURE

Payload Contract Testing

Testing in Semitexa can start from the transport boundary itself: payloads declare what should be verified, and the framework runs the strategy suite.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Automated contract testing for payloads — #[TestablePayload] marks a payload for strategy-based validation.

How it works

The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint.

Why it matters

Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance.

Key concepts

#[TestablePayload]
Marks a payload for automated contract testing with configurable strategies.
MonkeyTestingStrategy
Sends random/malformed input to test endpoint robustness.

Contract Testing

One scaffolded suite can exercise every testable payload

Instead of hand-writing the same validation and bad-input cases for every endpoint, Semitexa lets payloads declare their testing strategies and runs the composed suite through a shared transport.

Scaffold once. test:init creates the universal ProjectPayloadsContractTest that auto-discovers payloads marked with #[TestablePayload].

Choose a profile. Strict and Paranoid profiles expand into concrete strategies like method checks, type mutation, security assertions, and monkey input.

Run through real transport. The contract tester sends actual requests and reports which strategy or generated case failed instead of hiding everything in custom helpers.

Command Purpose Why it matters
bin/semitexa test:init Scaffold the universal payload contract test into tests/Payload. Gives the project one canonical entrypoint for payload boundary verification.
bin/semitexa test:run -- --filter ProjectPayloadsContractTest Run only the generated contract suite inside the dev Docker stack. Keeps feedback focused while expanding coverage across all marked payloads.
bin/semitexa test:run -- --testdox Forward normal PHPUnit flags through the Semitexa wrapper. Developers keep PHPUnit ergonomics while preserving the project container setup.

Mark a payload as contract-testable

#[AsPublicPayload(path: '/api/payments', methods: ['POST'])]
#[TestablePayload(
    strategies: [StrictProfileStrategy::class, ParanoidProfileStrategy::class],
    context: ['fail_fast' => false],
)]
final class PaymentPayload {}

Scaffold and run the suite

bin/semitexa test:init
bin/semitexa test:run -- --filter ProjectPayloadsContractTest

What Paranoid profile expands to

ParanoidProfileStrategy::class => [
    SecurityStrategy::class,
    HttpMethodStrategy::class,
    TypeEnforcementStrategy::class,
    MonkeyTestingStrategy::class,
]
#[TestablePayload] test:run StrictProfileStrategy MonkeyTestingStrategy

Verified against Semitexa Ultimate 2026.09.19.1020

Payload Contract Testing

Automated contract testing for payloads -- #[TestablePayload] marks a payload for strategy-based validation.

How it works

The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint. The semitexa-testing package ships its own ProjectPayloadsContractTest integration test, and the canonical runner bin/semitexa test:run appends that suite automatically from vendor/semitexa/testing/tests/Integration/ProjectPayloadsContractTest.php in consuming projects. That keeps payload-contract coverage enabled without scaffolding a root-level tests/ bucket, but it also means direct vendor/bin/phpunit runs are not the supported entry point.

Why this matters

Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance -- Strict catches common boundary mistakes, while Paranoid adds monkey input and deeper type mutation coverage.

© Brian Kernighan: "Controlling complexity is the essence of computer programming."

#[TestablePayload] Trusted input boundary
<?phpdeclare(strict_types=1);namespace Semitexa\Testing\Attributes;use Attribute;/** * Declares testing strategies for a PayloadDTO class. * * Intentionally separate from #[AsPayload] to avoid loading test classes * during production bootstrap (SRP — routing ≠ testing). * * Example: * ```php * #[AsProtectedPayload(path: '/api/payments', methods: ['POST'])] * #[TestablePayload( *     strategies: [ParanoidProfileStrategy::class], *     context: [ *         'auth_header'    => 'Authorization', *         'auth_scheme'    => 'Bearer', *         'token_provider' => TestTokenProvider::class, *     ] * )] * class PaymentPayload implements PayloadInterface { } * ``` * * The `context` map is passed as-is to each strategy via PayloadMetadata::$context. * Each strategy reads only the keys it cares about; unknown keys are ignored. */#[Attribute(Attribute::TARGET_CLASS)]final class TestablePayload{    /**     * @param list<class-string<\Semitexa\Testing\Contract\TestingStrategyInterface>> $strategies     * @param array<string, mixed> $context  Per-strategy configuration (auth tokens, flags, etc.)     */    public function __construct(        public readonly array $strategies = [],        public readonly array $context = [],    ) {}}

Testing Posture

When this pays off the most

Contract testing is strongest when the payload really is the transport boundary and the team wants broad negative coverage without a pile of custom boilerplate.

Use Strict or Paranoid profiles on auth, money, or mutation endpoints where malformed input should never leak into handler logic.

Keep the payload as the real boundary object; contract testing loses value if the handler still parses raw arrays or globals.

Prefer one generated suite plus strategy profiles over dozens of repetitive “422 when field is missing” tests.

Let the contract suite complement domain tests, not replace them; payload robustness and business behavior are different concerns.

How it works

The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint.

Why it matters

Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance.

Key concepts

#[TestablePayload]
Marks a payload for automated contract testing with configurable strategies.
MonkeyTestingStrategy
Sends random/malformed input to test endpoint robustness.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal