codeception/module-lumen
Codeception module for testing Lumen applications, providing Lumen-aware helpers and integration for functional, API, and database tests within the Codeception framework.
This package is the Codeception module that boots a Laravel application inside acceptance/functional tests, so you can drive your app through HTTP-ish browser steps and Laravel-aware helpers instead of raw PHPUnit assertions.
composer require --dev codeception/module-laravel codeception/codeception
vendor/bin/codecept bootstrap
tests/Acceptance.suite.yml (and/or Functional.suite.yml) and point it at your bootstrap:
modules:
enabled:
- \Helper\Acceptance
- Laravel:
environment_file: .env.testing
bootstrap: _bootstrap.php
cleanup: true
tests/_bootstrap.php that requires vendor/autoload.php and bootstrap/app.php so the module can resolve the app.tests/Acceptance/:
public function testDashboardRequiresLogin(AcceptanceTester $I): void
{
$I->amOnRoute('dashboard');
$I->seeCurrentRouteIs('login');
}
vendor/bin/codecept run Acceptance (add --debug for step-by-step output; that's where you'll live while debugging).Look first at the module's configuration options (environment_file, bootstrap, cleanup, transaction, url) and the _support/Helper/Acceptance.php file — that helper is the intended extension point for app-specific actions.
Note on 3.0.1: the only change is compatibility with codeception/lib-innerbrowser v4. That's a dependency-alignment release, not an API change — your existing tests keep working. It matters if you're on a modern stack (BrowserKit 6/Symfony 6+, PHP 8.1+, Codeception 5): lib-innerbrowser v3 is now the "old" branch, so 3.0.1 lets you stay current without pinning an unsupported version.
Route-first, not URL-first. Prefer amOnRoute('users.show', ['user' => 1]) over amOnPage('/users/1'). Route names survive URL refactors, and failures point at the route that broke rather than a 404 on a string.
Auth shortcuts instead of login flows. Use the module's Laravel-aware session actions (amLoggedAs($user), amGuest()) rather than replaying your login form in every test. Keep exactly one real login test that exercises the form; everything else authenticates directly. This cuts seconds off each test and removes the login page as a shared point of failure.
Two-tier suite layout. Structure like this:
Acceptance — full HTTP through the app: forms, redirects, cookies, flash messages, middleware, CSRF.Functional — same Laravel module but asserting behaviour closer to the code: events fired, jobs queued, mail sent, records created.Unit — plain PHPUnit, no module.Keeping functional assertions out of acceptance tests is the single biggest speed and stability win.
Database strategy: pick one and be consistent. Either:
cleanup: true + transaction: true with DatabaseTransactions on your test case (fast, rolls back per test), orcleanup: true + migrate/seed via a bootstrap or a _before hook (slower but works across connections).The transaction route breaks if your app writes over a second connection or spawns queue workers that use their own connection. If you have those, isolate them into functional tests with explicit cleanup.
Model factories beat fixtures. Use $I->have(User::class, ['role' => 'admin'])-style helpers in your Acceptance helper to wrap factory()->create(). This keeps tests readable and avoids RefreshDatabase semantics fighting the module's own lifecycle.
Wrap repeated journeys in the helper. When three tests click the same five things, add a method to _support/Helper/Acceptance.php (loginAsAdmin(), submitCheckoutForm()). It's plain PHP with the actor injected, so you get full IDE completion and one place to fix when the UI shifts.
Assert side effects with module actions, not raw DB queries. Prefer the module's record assertions (seeRecord, dontSeeRecord, grabRecord) and its mail/queue/event assertions over DB::table(...)->count() inside the test. They read better in failure output and respect the module's connection handling.
Isolate non-deterministic integrations. Fake mail, notifications, and queue in _before (Mail::fake(), Queue::fake()) and assert with the module's helpers. One end-to-end test without fakes, tagged or in a separate suite, catches wiring mistakes.
CI wiring. Run Codeception as a separate CI job/step from PHPUnit; a failing acceptance run should not mask unit failures. Use --env=ci with a suite-level _envs/ci.suite.yml override so local debug settings (--debug, cleanup: false) never leak into CI.
Environment file drift. environment_file: .env.testing is easy to set and easy to forget in CI. If the file is missing, the module silently falls back to the default env and your tests may hit real services. Add a bootstrap assertion that app()->environment() === 'testing' and fail loudly.
Config and route caching bite hard. A stale bootstrap/cache/config.php or routes-*.php from a previous local run will be loaded by the module and mask your .env.testing values. Clear caches in the bootstrap (or in a CI step) rather than debugging mystery config values.
APP_KEY and encryption. Encrypted cookies/sessions need a key. If .env.testing lacks APP_KEY, you'll see seemingly random session loss between steps. Generate one into .env.testing and commit it — it's a test key, not a secret.
The app boots per test, not per step. State inside one test (session, cookies, container-bound singletons) persists across $I-> steps; between tests you get a fresh application. Don't try to reuse a logged-in state across test methods, and don't mutate the container in a way you expect to survive to the next test.
Session and cookie leakage between tests. If you rely on cleanup: false for speed, cookies, auth state, and database rows persist. That turns ordering into a hidden dependency and produces tests that pass alone and fail in a suite. Default to cleanup: true and only relax it deliberately.
Queue workers and transactions don't mix. A transaction: true rollback can't undo writes made by a synchronously dispatched job using a different connection. Either use Queue::fake() and assert the push, or run those tests without the transaction wrapper.
CSRF and middleware in acceptance tests. Some suites disable middleware to skip CSRF; that also disables auth, throttling, and your own middleware, which quietly invalidates the test. If you disable middleware, keep a separate suite or test that runs with it enabled — that's the one that catches the regression where the VerifyCsrfToken exception list was edited.
lib-innerbrowser v4 constraints (new in 3.0.1). v4 pulls newer BrowserKit; if your composer.json pins symfony/browser-kit or symfony/dom-crawler to an older major, the resolver will conflict. If you can't move yet, either stay on module 3.0.0 or pin codeception/lib-innerbrowser:^3 — but expect that branch to fall out of support. Verify with composer why codeception/lib-innerbrowser before upgrading, and run the acceptance suite (not just unit) as the upgrade gate: BrowserKit changes affect form submission and crawler-based assertions, which is exactly the surface that version bump touches.
Assertion timing. The Laravel-aware mail/queue/event assertions typically run against the app the module booted; if you build a custom helper that resolves a fresh app from the container, you'll assert on the wrong instance and get false negatives. Always go through the module ($I->...) or the injected application rather than app() inside a helper.
Extension points, in order of preference: the suite Helper/*.php for reusable actions, _bootstrap.php for environment setup, and _envs/*.suite.yml for environment overrides. Avoid subclassing the module — internal method names have shifted across majors, and that's how upgrades become painful.
Debugging workflow that actually works. Run the single failing test with --debug to see each step's request/response, then temporarily $I->pause() (with --debug) to inspect state, and check the Laravel log at storage/logs/laravel.log — the module runs your app's real exception handler, so swallowed exceptions show up there rather than in the test output.
How can I help you explore Laravel packages today?