Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mbt Bundle Laravel Package

tienvx/mbt-bundle

tienvx/mbt-bundle is a Symfony bundle for model-based testing in PHP. Define state-machine models, generate executable test cases, and run them with PHPUnit to add structured MBT coverage to existing test suites.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing first. tienvx/mbt-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config/mbt.php, and no facade. It is the engine behind Sicope Model — you draw a state machine (places + transitions) in the Sicope web UI, export or hand-write it, and this bundle turns that model into runnable PHPUnit test cases. Treat it as a dev-only dependency, not a runtime one.

Minimal steps to get a first result:

  1. composer require --dev tienvx/mbt-bundle symfony/workflow — the Symfony Workflow component is the engine that actually walks your model, and you must pull it in explicitly.
  2. Pick a small flow to model first: login → dashboard → logout, or a cart add/remove/checkout. Anything larger and you'll spend your first day fighting the model instead of learning the API.
  3. Look at these classes, in this order, before reading anything else:
    • Model — your state machine definition (places, transitions, metadata).
    • Test — the generated test representation.
    • TestGenerator / ModelGenerator — the code that emits PHPUnit classes from a model.
    • The command classes under src/Command — run bin/console list mbt (or php artisan list if you've registered the bundle in a Laravel app, see below) to see what's actually available in your pinned version.
  4. Generate a test from the model and run it once against a throwaway endpoint. The goal of the first pass is to see the shape of the generated test, because that shape dictates everything you do afterwards.

If you're in a Laravel app, the cheapest bridge is a standalone Symfony console kernel in tools/mbt/ (or a separate composer.json in a tests/mbt subdir). Registering a Symfony bundle inside artisan is possible but almost never worth it — you gain nothing from the console plumbing and lose an afternoon to Kernel bootstrapping.

Implementation Patterns

Model-as-code, not model-as-drawing. The tempting workflow is "design in Sicope, export, generate." In practice teams converge on committing the model as a PHP class and using Sicope only for visualization. The model file then becomes reviewable in PRs, diffable, and greppable — which the exported JSON is not.

Page objects as the transition layer. The bundle generates the walk (which transitions fire, in what order); your job is supplying a callable per transition that actually drives the app. Keep these thin:

// tests/Mbt/LoginFlow.php
class LoginFlow extends Model
{
    public function transitions(): array
    {
        return [
            'submit_credentials' => fn (TestCase $t) => $t->post('/login', [...]),
            'see_dashboard'      => fn (TestCase $t) => $t->assertSee('Dashboard'),
            'logout'             => fn (TestCase $t) => $t->post('/logout'),
        ];
    }
}

Then the generated test just sequences calls. This keeps model changes and UI changes in separate commits.

Use Laravel's HTTP test client, not Mink/Selenium, unless you need JS. The bundle is unopinionated about the driver. For a Laravel app, $this->actingAs($user)->get(...) and $this->post(...) inside a RefreshDatabase test is far faster and less flaky than bolting on a browser. Reserve a browser driver for the flows that genuinely require JS.

Generate, then curate. Don't regenerate in a loop on every CI run. Generate once, review the emitted test, then either commit it or wrap it in a thin subclass that adds your RefreshDatabase trait, factories, and seeding. Generated tests are scaffolding — the moment you hand-edit them, treat regeneration as a migration, not a build step.

Seed and control the walk. Model-based testing's whole selling point is exploring paths you wouldn't write by hand, but unseeded random walks produce non-reproducible failures. Fix the seed in config (or the model's metadata) and log it on failure so a red build is replayable.

Layer assertions in two places. Assert transition preconditions inside the transition callables (did the page actually load?), and assert end-state invariants after the walk. Cramming everything into the callable makes failures point at the walker instead of your app.

CI integration. Run generated tests as a normal PHPUnit suite (--testsuite=mbt or a separate phpunit.mbt.xml). Mark them non-blocking initially — model-based suites are noisier than hand-written ones while the model is under-built.

Gotchas and Tips

No Laravel support, and that's the headline. No service provider, no publishable config, no artisan commands out of the box. Every integration point is a small amount of glue you write and own. Budget for that.

Stale by design. Last release July 2022, ~18 stars, near-solo maintenance. Pin an exact version in composer.json and read the changelog before bumping. Symfony 6.3+/PHP 8.3 compat is not guaranteed in older tags — check the composer.json constraints in the tag you install, not the docs.

Symfony Workflow is not optional and not forgiving. Your model must declare an explicit marking_store and an initial marking; an empty initial state silently produces a test that walks nothing and passes. If a generated test "passes instantly," suspect the marking first.

Isolate state between transitions. The walker fires many requests in a single PHPUnit test. Any transition that writes to the DB will contaminate the next unless you reset per-transition or design transitions to be idempotent. RefreshDatabase per test method is not enough here — it only runs once for the whole walk.

Class-name collisions. The package's Test and Model classes are generic enough to clash with PHPUnit's TestCase and Laravel's own test stubs. Always import with aliases (use Tienvx\MbtBundle\Model\Model as MbtModel;).

Debugging a walk: dump the graph. Before blaming your transitions, confirm the graph is what you think it is. Use the package's graph/dump command if it exists in your version, or wire Symfony Workflow's GraphvizDumper — a rendered PNG resolves "why did it never reach checkout?" in seconds.

Extension points worth knowing: custom model contexts (to inject fixtures/factories), event hooks around transition execution (useful for logging the current marking on failure), and the generators themselves — forking TestGenerator to emit a RefreshDatabase trait and your base test class is a legitimate, low-risk customization.

Don't model your whole app. Coverage-per-model drops sharply once a model exceeds ~10 places. Several small models (auth, cart, billing) compose better, generate smaller tests, and fail with clearer messages than one hero model.

Verdict for a Laravel team: worth a spike if you have a stable, well-understood flow with expensive combinatorial paths (checkout permutations, permission matrices). Not worth it for CRUD — the glue-to-value ratio flips against you.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums