Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Takeshi's avatar

Laravel: how to write test for route Marco, Middleware and model binding

I build a simple way to identify tenant with subdomain: for example: user1.example.com, the tenant will be user1.

On the other hand, if user1 does not exist, the user1.example.com will response 404.

I define a Marco in service provider like following:

public function register()
{
    $this->app->singleton('tenancy', function (Application $app) {
        return new TenancyMananger($app);
    });
}

public function boot()
{
    Route::macro('tenancy', fn (Closure $groups) => Route::domain(sprintf('{tenant}.example.com'))
        ->middleware(['tenancy'])
        ->group($groups));

    Route::model('tenant', Tenant::class);
}

Then, use middleware to init and identify the tenant:

public function handle(Request $request, Closure $next)
{
    Tenancy::init($request->tenant);

    return $next($request);
}

And I override the getRouteKeyName method on the Tenant model to use database column other than id:

public function getRouteKeyName()
{
    return 'domain';
}

Finally, I write the following feature testing case to test it:

Route::tenancy(function () {
    Route::get('/test', fn () => 'hello world');
});

$tenant = Tenant::factory()->create([
    'domain' => 'demo',
    'name' => 'demo name',
]);

$this->get(""demo.example.com/test')->assertStatus(200);

$this->assertEquals(Tenancy::tenant()->id, $tenant->id);
$this->assertEquals(Tenancy::domain(), $tenant->domain);
$this->assertEquals(Tenancy::name(), $tenant->name);

But I will get the error:

testing.ERROR: App\TenancyMananger::init(): Argument #1 ($tenant) must be of type App\Models\Tenant, string given

It seems like that route does not trigger the model binding so that $request->tenant int the middleware pass the string ( subdomain ) into the TenancyMananger.

Any idea what could be happening here? Or I can not write the test like this...

Any help or guidance is much appreciated.

I use PHP 8.1, Laravel 9 and phpunit v9.5.27.

Thanks.

0 likes
1 reply
martinbean's avatar

@takeshi You really don’t need to do all that. You can just use route–model binding with the subdomain parameter.

Route::domain('{tenant:domain}.example.com')->group(function () {
    // Tenant routes...
})->where(['tenant' => '.*']);

No need for macros, custom bindings, middleware, overloading core Eloquent methods, etc.

Please or to participate in this conversation.