---
title: "For applications — Testing for Laravel | Roundly"
description: "Use the same assertions in a plain Laravel app — no package and no base-class change — through Pest or the static Assert."
url: https://roundly-consulting.com/open-source/docs/testing-for-laravel/applications
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Testing for Laravel](https://roundly-consulting.com/open-source/docs/testing-for-laravel.md)

# For applications

A plain Laravel app — no Roundly package, no base-class change — gets the app-usable subset through the Pest expectations or the static Assert, straight from its existing suite:

```php
use RoundlyConsulting\Testing\Assert;

it('has a runnable migration order', function (): void {
    expect(database_path('migrations'))->toHaveRunnableMigrationOrder();
});

it('reads every services key it relies on from the shipped config', function (): void {
    // Forward-only: an app's config legitimately carries keys read by vendor packages.
    expect(config_path('services.php'))->toSatisfyConfigContract(app_path(), ['reverse' => false]);
});

it('does not leak credentials through artisan about', function (): void {
    // Only a secret that is set can be looked for — a null or empty entry throws.
    config(['services.stripe.secret' => 'sk_test_do_not_render_me']);

    expect('environment')->toLeakNoSecrets(
        secrets: [config('app.key'), config('services.stripe.secret')],
        mustRender: ['Application Name'],
    );
});
```

## What applies to apps

- The migration-order pin, the real-engine runner and its negative control.
- The config contract in forward-only mode — reverse is opt-in.
- The secret-safe about capture.
- The model-swap proof — apps consume config-swappable vendor models too, so you can prove a vendor package really listens.
- The lock recorders and DriverMatrix.
- Every architecture preset. runtimeRequireIsWhitelisted encodes the Roundly whitelist, but $alsoAllow makes even that usable.

## Architecture presets in an app

noDebuggingLeftovers scans <cwd>/src by default, so point it at app/ in an application:

```php
<?php

declare(strict_types=1);

use RoundlyConsulting\Testing\Arch\ArchPresets;

// tests/ArchTest.php in a Laravel application
ArchPresets::strictTypes('App');
ArchPresets::noDebuggingLeftovers(srcDir: __DIR__.'/../app'); // the default scans <cwd>/src
```

## Plain PHPUnit: the static Assert

Every expectation has a static mirror on RoundlyConsulting\\Testing\\Assert for class-based tests — toBeSwappableVia and the facade contract included. Both delegate to the same implementation, so the check is identical however you reach it:

| Pest expectation | Static mirror |
| --- | --- |
| `toHaveRunnableMigrationOrder` | `Assert::migrationsRunInDependencyOrder($dir, $expectedForeignKeys, $tableResolvers, $externalTables)` |
| `toApplyOnConnection` | `Assert::migrationsApplyOnConnection($dir, $connection, $expectedMigrations)` |
| `toRejectBrokenOrderOnConnection` | `Assert::brokenOrderIsRejectedOnConnection($dir, $reorder, $connection)` |
| `toNotAutoLoadMigrations` | `Assert::doesNotAutoLoadMigrations($providerClass, $migrationsDir)` |
| `toPublishMigrationsTimestamped` | `Assert::publishesMigrationsTimestamped($providerClass, $tag, $count)` |
| `toSatisfyConfigContract` | `Assert::configContract($configPath, $srcDirs, $prefix, $options)` |
| `toLeakNoSecrets` | `Assert::aboutSectionLeaksNoSecrets($section, $secrets, $mustRender)` |
| `toHonourModelSwap` | `Assert::modelSwapHonoured($configKey, $subclass, $exercise, $expectsCreation)` |
| `toBeSwappableVia` | `Assert::modelIsSwappableVia($model, $configKey)` |
| `toDocumentItsRoot` | `Assert::facadeDocumentsItsRoot($facade, $except)` |
| `toBeFakeable` | `Assert::facadeIsFakeable($facade)` |
| `toReachEveryAction` | `Assert::facadeReachesEveryAction($facade, $actionsDir, $except, $via)` |
| `toRedactSensitiveArguments` | `Assert::facadeRedactsSensitiveArguments($facade, $methods)` |

```php
use PHPUnit\Framework\Attributes\Test;
use RoundlyConsulting\Testing\Assert;
use Tests\TestCase;

final class SchemaTest extends TestCase
{
    #[Test]
    public function migrations_run_in_dependency_order(): void
    {
        Assert::migrationsRunInDependencyOrder(database_path('migrations'));
    }

    #[Test]
    public function the_services_config_ships_every_key_the_app_reads(): void
    {
        Assert::configContract(config_path('services.php'), app_path(), options: ['reverse' => false]);
    }
}
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/testing-for-laravel.md)
