codeception/module-laravel
Codeception module that integrates the Codeception testing framework with Laravel, adding helpers for database transactions, authentication, mail, events, queues, and HTTP tests so you can write expressive functional and unit tests for your app.
codeception/module-laravel is the bridge that boots your real Laravel application inside Codeception so you can drive it in-process — no web server, no browser, no Illuminate\Foundation\Testing\TestCase. Think of it as "Laravel feature tests, but with Codeception's suite runner, helpers and reporting."
composer require --dev codeception/codeception:^5 codeception/module-laravel:^3.2 --with-all-dependencies
vendor/bin/codecept bootstrap # creates tests/ and codeception.yml
Enable the module in tests/functional.suite.yml:
actor: FunctionalTester
modules:
enabled:
- Asserts
- Laravel:
environment_file: .env.testing
cleanup: true # wrap each test in a DB transaction
reboot: true # fresh container per test
Add a tests/.env.testing pointing at a throwaway database (DB_CONNECTION=sqlite, DB_DATABASE=:memory: is the fastest start), then your first use case — a route that requires auth:
// tests/Functional/DashboardCest.php
public function showsDashboardForMember(FunctionalTester $I): void
{
$user = $I->have(User::class, ['plan' => 'pro']);
$I->amLoggedAs($user);
$I->amOnRoute('dashboard');
$I->see('Your plan: pro');
}
Where to look first: tests/_support/Helper/Functional.php (your domain helpers), the module's src/Codeception/Module/Laravel.php (the full action list), and your config/auth.php defaults — auth is the single most common source of surprises.
Prefer functional suites over hand-rolled feature tests. $I->amOnRoute('orders.show', ['order' => $order->id]) dispatches a real HTTP kernel request with middleware, form requests, policies and Blade rendering. You get the confidence of RefreshDatabase feature tests with Codeception's --steps, --html, and Cest/Gherkin structure.
Auth is the bread and butter. amLoggedAs($user) uses the default guard; pass a second argument for named guards (amLoggedAs($admin, 'admin')). Pair with seeAuthentication() / dontSeeAuthentication() / seeAuthenticatedAs() to assert state, and always exercise at least one test per guard so a misconfigured config/auth.php in the testing env surfaces immediately.
Factories, not fixtures. $I->have(User::class, ['role' => 'admin']) and $I->haveMultiple(Order::class, 3) run your Eloquent factories, so overrides and states stay in one place. Use the module's populator option for reference data that every test needs (countries, roles, permissions) instead of seeding in each test.
Console and scheduling. $I->callArtisan('orders:purge --days=30') lets you assert command output and side effects without shelling out. Alternatively set run_database_migrations: true (or database_migrations_path) so the schema is rebuilt per suite rather than per test.
Container-level mocking. $I->mockService(BillingGateway::class, $fake) swaps the binding in the booted app for that test — the right tool for third-party HTTP clients, and cleaner than $this->mock() because it exercises your real service provider wiring.
Control side effects explicitly. $I->disableEvents() (and enableEvents()) prevents listeners from firing mail, jobs or webhooks during a test; $I->expectEvents(OrderPlaced::class) asserts an event was dispatched without asserting on the listener. $I->expectDatabaseChange() (3.x) is a neat way to assert "this action writes to orders" without brittle row assertions.
Build a domain DSL in your Helper. Once you find yourself repeating have(User::class, [...]) + amLoggedAs + amOnRoute, move it into tests/_support/Helper/Functional.php as loginAsAdmin(), createPaidOrder() etc. This is the biggest maintainability lever in a Codeception suite.
Layer the pyramid deliberately. Functional (this module) for request→response behaviour; acceptance with codeception/module-webdriver only for JS-dependent flows; plain PHPUnit for pure units. Don't test the same thing twice.
3.2.1 — guard name implicit null. Prior to this patch the module could pass an implicitly-null guard through to the auth factory, which broke (or silently fell back to the default guard) when you had a custom viaRequest provider or a null default guard in config/auth.php. If you have auth tests that only passed because of that fallback, re-run them; the safe pattern is to always name the guard explicitly when it isn't web. Verify defaults.guard in your .env.testing-loaded config.
cleanup: true uses transactions. Any DDL inside a test (calling migrate or migrate:fresh via callArtisan with SQLite/MySQL) will implicitly commit and defeat the rollback. Do migrations once in _bootstrap or via module config, never inside a test.
reboot: true is isolation, false is speed. Rebooting the container per test is the correct default but expensive on large suites. If you disable it, remember singletons and static caches persist across tests — a classic source of "passes alone, fails in suite".
amLoggedAs doesn't fire the Login event. Session regeneration, last_login_at updates, MFA challenges and audit listeners won't run. If those matter, POST the login form instead of using amLoggedAs.
CSRF and middleware. Use $I->disableMiddleware() when you want to isolate a controller, but keep at least one suite running with middleware enabled — otherwise a broken VerifyCsrfToken exclusion or a missing auth middleware goes unnoticed.
Config caching breaks environment_file. If CI runs php artisan config:cache before tests, tests/.env.testing is ignored. Add config:clear / cache:clear to _bootstrap and set APP_ENV=testing in the Codeception env.
Don't mix worlds. Using Illuminate\Foundation\Testing\RefreshDatabase, actingAs() or WithoutMiddleware alongside this module leads to double bootstrapping and confusing failures. Pick Codeception's modules (Laravel, Db, Asserts) or Laravel's own test traits — not both in the same suite.
Extension points. Subclass Codeception\Module\Laravel and override _before/_after for cross-cutting setup; call $this->getApplication() inside your helper to reach the container; use Codeception\Extension for suite-level hooks (DB snapshots, reference-data seeding, per-worker database naming for parallel runs). For parallelisation, give each worker its own database name — a shared DB plus cleanup: true produces flaky, order-dependent failures.
Debugging. vendor/bin/codecept run functional --steps --debug prints every module action; exceptions thrown inside the app often surface as a generic failure, so check tests/_output/ and storage/logs/laravel.log — and confirm APP_DEBUG=true in the testing env while diagnosing. Version note: 3.x requires PHP 8.1+ and Codeception 5; stay on module 2.x if you're pinned to Codeception 4.
How can I help you explore Laravel packages today?