›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-dev-login
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

byrcsc/laravel-dev-login · 1.x

Testing.

Drive a profile from a test, and turn the page on for browser tests that click its buttons.

Laravel's actingAs() is still the right tool for authenticating in a feature test. Reach for this package in a test when the thing under test is the profile itself: a resolver of yours, a tenant resolver, or a browser test that clicks a real button.

testing is on the default environment allowlist, so the package is allowed to run in a test suite. The enable flag still has to say yes.

Driving a profile without HTTP

Authenticator takes a Profile and returns the authenticated user. It needs no routes, so the gates never come into it:

use ByRcsc\LaravelDevLogin\Authenticator;
use ByRcsc\LaravelDevLogin\Profile;

it('logs in through my resolver', function (): void {
    User::factory()->create(['email' => 'admin@example.com']);

    app(Authenticator::class)->login(new Profile(
        key: 'admin',
        label: 'Admin',
        email: 'admin@example.com',
        resolver: App\DevLogin\FindUserByUsername::class,
    ));

    expect(auth()->check())->toBeTrue();
});

Every profile key is available as a constructor argument, so a test can build a shape that is not in your config file.

This is the seam to use for testing a UserResolver or a TenantResolver. Assert on your own resolver's side effects rather than on the redirect, which belongs to the controller.

Config the authenticator reads at call time, dev-login.resolver and dev-login.tenant_resolver, can be set with a plain config()->set() inside the test. It is only the gates that are decided earlier.

Testing your tenant resolver

A resolver is a class with one method, so most of it needs no framework at all. Where it does, drive it through Authenticator to get the ordering the real flow gives it:

use ByRcsc\LaravelDevLogin\Authenticator;
use ByRcsc\LaravelDevLogin\Profile;

it('resolves the user inside the tenant', function (): void {
    config()->set('dev-login.tenant_resolver', App\DevLogin\MakeTenantCurrent::class);

    app(Authenticator::class)->login(new Profile(
        key: 'acme-owner',
        label: 'Owner',
        email: 'owner@acme.test',
        tenant: 'acme',
    ));

    expect(Tenant::current()?->slug)->toBe('acme');
});

The tenant is made current before the user is resolved, so a resolver that asserts what was current when it ran is asserting the contract's promise.

Turning the page on for a test

The gates are read while the application boots, and route registration is decided there. A config()->set('dev-login.enabled', true) inside a test body lands too late: the routes are already absent.

Set it before the application boots instead. In phpunit.xml:

<php>
    <env name="DEV_LOGIN_ENABLED" value="true"/>
</php>

Or in .env.testing:

DEV_LOGIN_ENABLED=true

Both reach enabled through the same env() call the config file makes.

Requests in Laravel's test suite arrive on localhost by default, which is on the shipped allowed-hosts list. If your suite sets APP_URL to something else, add that host to allowed_hosts.

Asserting the page and a click

With the flag on, the routes behave like any others:

it('shows a button per profile', function (): void {
    config()->set('dev-login.profiles', [
        'admin' => ['label' => 'Admin', 'email' => 'admin@example.com'],
    ]);

    $this->get('/dev-login')->assertOk()->assertSee('Admin');
});

Profiles can be set inside the test body, because ProfileRepository reads config on every call rather than at boot. Only the gates need to be decided earlier.

use Illuminate\Auth\Events\Login;
use Illuminate\Support\Facades\Event;

it('authenticates and fires the login event', function (): void {
    User::factory()->create(['email' => 'admin@example.com']);

    config()->set('dev-login.profiles', [
        'admin' => ['label' => 'Admin', 'email' => 'admin@example.com'],
    ]);

    Event::fake([Login::class]);

    $this->post('/dev-login/admin')->assertRedirect('/');

    Event::assertDispatched(Login::class);
    expect(auth()->check())->toBeTrue();
});

GET /dev-login/{profile} returns 405. There is no login-by-GET route to assert against.

Browser tests

A browser test can click a real button, which is the one thing a feature test cannot check. It needs the same three things any other request does: the enable flag on, an environment on the allowlist, and the host the browser uses on allowed_hosts.

The host is the part that catches people out. A browser driver visits the application over a real URL, so whatever APP_URL resolves to has to be on the list. 127.0.0.1 and localhost are already there.

Testing your own gate configuration

Gatekeeper answers from live config, so a test can assert an allowed-hosts list without booting anything:

use ByRcsc\LaravelDevLogin\Gatekeeper;

it('allows our review apps', function (): void {
    config()->set('dev-login.allowed_hosts', ['*.review.example.com']);

    expect(app(Gatekeeper::class)->hostIsAllowed('pr-42.review.example.com'))->toBeTrue()
        ->and(app(Gatekeeper::class)->hostIsAllowed('review.example.com'))->toBeFalse();
});

passes() reads the environment through the application, so a test that changes app.env has to rebuild the application for it to take effect.

What to read next

  • The login flow for what Authenticator::login() does.
  • Safety gates for which checks run at boot and which run per request.
  • Troubleshooting if a test gets a 404 it did not expect.
PreviousConfigurationNextTroubleshooting
View source

On this page

  1. Driving a profile without HTTP
  2. Testing your tenant resolver
  3. Turning the page on for a test
  4. Asserting the page and a click
  5. Browser tests
  6. Testing your own gate configuration
  7. What to read next