PostgreSQL Permissions for Laravel Tests: Fix Errors12 min read2,364 words

PostgreSQL Permissions for Laravel Tests: Fix Errors

Fix PostgreSQL permission errors in Laravel tests: isolate a test database, grant schema access, configure PHPUnit, and use RefreshDatabase safely. Start here.

PostgreSQL permission errors Laravel testsLaravel PostgreSQL testingPostgreSQL schema permissionsLaravel RefreshDatabasePHPUnit test database
PostgreSQL Permissions for Laravel Tests: Fix Errors

Fix PostgreSQL Permissions Before Running Laravel Tests

A Laravel feature test can fail before it ever touches your application code. One common culprit is PostgreSQL blocking Laravel's attempt to create its migrations table, with an error like permission denied for schema public.

In other words: the database connection may work, and the role may even be able to access the database, but that still doesn't mean it can create tables inside the public schema.

The reliable fix is to treat test setup like real infrastructure: use a dedicated test database, grant the configured role the permissions it needs there, and make sure PHPUnit and Laravel are pointed at the same database. Then use Laravel's RefreshDatabase trait when you need a predictable database state. This guide walks through those steps, clarifies the difference between database-level and schema-level permissions, and shows how to tell a genuine PostgreSQL failure from unrelated PHP deprecation warnings. It also covers what a passing setup test does - and doesn't - prove about your API.

Why Laravel gets a PostgreSQL permission error

Say you generate a Laravel feature test and run it. Before the test can make an HTTP request or assert on a response, Laravel first prepares the database. That often includes creating (or updating) the migration history table.

Then PostgreSQL responds with something like:

SQLSTATE[42501]: Insufficient privilege: 7

ERROR: permission denied for schema public

SQL: create table "migrations" (...)

This usually isn't "a bad migration" or "a broken test." It's PostgreSQL enforcing a permission boundary. PostgreSQL permissions aren't one-size-fits-all: a role can be able to connect to a database and still be missing the permissions required to create objects in a specific schema.

Database access and schema access are different

Here's the split that matters for this problem:

  • Database privileges cover things like connecting to a database.
  • Schema privileges cover actions inside a namespace in the database - like creating objects in that schema.
  • Table privileges cover actions on specific tables, like reading and updating rows.

Creating a table is a schema-level operation. In a typical Laravel setup, migrations create tables in the public schema unless you've configured otherwise. So being allowed to connect to samu_test doesn't automatically mean the role can create a table in public.

Ownership can be the reason too. In the example that prompted this fix, the application role could access the test database, but the database and public schema were owned by the PostgreSQL administrator role. The application role couldn't create the migration table - so the test failed during setup, before it ever got to the assertions.

Why the test database should be separate

Tests that run migrations can create, alter, or roll back schema objects. Some cleanup flows can also remove data. Running that against a development database creates unnecessary risk: one misconfiguration can damage local data or make your tests depend on whatever happens to exist on that machine.

A dedicated database gives you a clean boundary. It also makes permissions easier to reason about: you can grant the test role exactly what it needs in the test database, without handing out broad access to production or other environments. The exact commands vary across PostgreSQL versions and hosting setups, but the principle stays the same: isolate test data and grant only what the test role needs.

Create and authorize a dedicated test database

Start by checking what databases exist and who owns them. On a locally managed PostgreSQL instance, you can list databases with:

sudo -u postgres psql -l

A simplified output might look like:

Name       | Owner

-----------+----------

postgres | postgres

samu | samu_user

samu_test | postgres

/samu_test exists, but the role used by your application (samu_user) doesn't own it. That might be intentional. The real question is whether the role Laravel uses can do the operations your test suite needs inside samu_test.

Choose between ownership and explicit grants

For a small dedicated test database, making the test role the owner can be the simplest option. That typically means running administrative commands using a PostgreSQL role that's allowed to change ownership. For example:

sudo -u postgres psql -c \

"ALTER DATABASE samu_test OWNER TO samu_user;"

sudo -u postgres psql -d samu_test -c \

"ALTER SCHEMA public OWNER TO samu_user;"

The first command changes the database owner. The second changes the owner of the public schema inside that database. These don't automatically imply ownership across everything else.

Ownership isn't the only way. You can also grant the required privileges to the test role, for example:

GRANT CONNECT ON DATABASE samu_test TO samu_user;

GRANT USAGE, CREATE ON SCHEMA public TO samu_user;

In general: run database-level commands while connected to an appropriate database, and run schema-level commands while connected to samu_test.

If your tests need to modify tables that were created by some other role, you may need extra table/sequence privileges - or you may need to stick to one consistent ownership strategy. Also note: granting CREATE on the schema doesn't automatically make the role the owner of objects that already exist there.

Which approach is "best" depends on your environment. For a throwaway database used only by a single test role, ownership is often easiest. In shared setups, explicit grants can match least-privilege requirements better. Just don't apply ownership changes to production unless you actually mean to.

Verify the schema, not just the database

After you change permissions, inspect the schema:

sudo -u postgres psql -d samu_test -c "\\dn+ public"

This shows the schema owner and access privileges. You can also test connectivity using the same role and database settings Laravel uses.

The point is simple: confirm the permission that caused the failure - can the test role create objects in the schema Laravel targets?

If the application role uses a password, follow your normal secret-management process. Don't put real credentials into source control or shell history. For more background on PostgreSQL's privilege model, see the PostgreSQL privileges documentation.

Point PHPUnit and Laravel at the test database

Fixing permissions won't help if the test runner connects to the wrong database. Make sure Laravel's test configuration matches your dedicated PostgreSQL setup: host, database name, user, and connection settings all need to line up.

Laravel projects typically define test settings in phpunit.xml, a .env.testing file, or both. Don't assume every project loads settings in the same order - check your Laravel version and current configuration.

For example, phpunit.xml might include:

<env name="APP_ENV" value="testing"/>

<env name="DB_CONNECTION" value="pgsql"/>

<env name="DB_HOST" value="127.0.0.1"/>

<env name="DB_PORT" value="5432"/>

<env name="DB_DATABASE" value="samu_test"/>

<env name="DB_USERNAME" value="samu_user"/>

If PostgreSQL requires password authentication, provide DB_PASSWORD via a local or CI secret. Don't commit a production password to phpunit.xml.

Setting What it controls
APP_ENV Selects Laravel's testing environment
DB_CONNECTION Chooses the database driver, here pgsql
DB_HOST Identifies the PostgreSQL server
DB_PORT Identifies the PostgreSQL port, commonly 5432
DB_DATABASE Selects the dedicated test database
DB_USERNAME Selects the PostgreSQL role used by the test process
DB_PASSWORD Supplies authentication when the server requires it

Check for configuration conflicts

If .env.testing exists, compare its database settings with what PHPUnit is configuring. A mismatch makes debugging painful: you can grant permissions for samu_test and still have tests connect somewhere else.

Also check whether config was cached. In local development, clearing Laravel's config cache helps after you change environment settings:

php artisan config:clear

In CI, verify the environment variables passed into the test job too. Values injected by the runner or container can differ from what you expect based on local files. Don't guess which connection is active - confirm what the test process is actually using.

Laravel's database testing documentation and configuration documentation are good references when behavior differs across versions or project setups.

Use RefreshDatabase and verify the result

Once the connection and permissions are correct, RefreshDatabase can set up the database state for tests. Add it to a feature test when the test depends on database state:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class MenuCategoryApiTest extends TestCase

{

use RefreshDatabase;

public function test_menu_categories_table_exists(): void

{

$this->assertDatabaseHas('migrations', [

'migration' => '2026_09_18_034452_create_menu_categories_table',

]);

}

}

RefreshDatabase prepares the test database using Laravel's migration and transaction mechanisms as appropriate for your app and test environment. It helps tests start from a known baseline, but it's not a substitute for correct PostgreSQL permissions and correct connection settings. Laravel still has to connect to the database you configured and run the migrations it needs.

Make the assertion match the question

That example assertion checks that a specific migration appears in Laravel's migrations table. It assumes the migration ran and that the filename matches the string in the test. This can be useful as a setup check, but it doesn't prove that your menu-category API behaves correctly.

If you're only trying to verify that a table exists, a schema-level assertion can express that intent more directly. If what you actually care about is application behavior, test behavior: make a request, check the response, and verify the resulting database state. A test that only confirms a migration record can still pass even if your endpoint has broken validation, authorization, or response logic.

After fixing configuration, run the focused test:

php artisan test --filter=MenuCategoryApiTest

If it passes, that's good evidence that Laravel could prepare the configured database and that the assertions succeeded. It's still not proof that the full suite or every API operation works - so once the focused test is green, run the whole suite:

php artisan test

Treat deprecation warnings as a separate issue

It's totally possible for tests to pass while PHP prints deprecation warnings. For example, a warning about implicitly nullable parameters in a package like nunomaduro/collision doesn't mean PostgreSQL failed to create tables.

Investigate package compatibility on its own by checking versions:

php -v

composer show nunomaduro/collision

composer show laravel/framework

composer show phpunit/phpunit

Before changing dependencies, verify which versions are compatible with your installed PHP, Laravel, and PHPUnit. Updating one package without checking the rest can swap one failure mode for another. Keep the diagnosis clear: a PostgreSQL permission error is a database access problem; a PHP deprecation warning is a compatibility/maintenance issue.

Extend tests beyond database setup

For a menu-category API, the next steps should test the behavior users actually rely on:

Test area What to verify
List categories The endpoint returns the expected records and response shape
Create category Valid input creates a record and returns the expected response
Validate input Invalid or missing fields produce the intended validation response
Show category The requested record is returned, and missing records are handled correctly
Update category Changes are persisted and reflected in the response
Delete category The record is removed or otherwise handled according to the API contract
Prevent duplicates The application enforces its intended uniqueness rules
Validate relationships Warehouse or kitchen-section references are checked and persisted correctly

You can test migration details separately (columns, foreign keys, constraints). Just keep the layers distinct: database setup tests confirm the environment is usable; feature tests confirm the application behaves correctly.

Conclusion

A PostgreSQL permission error during Laravel test setup usually means your test role can reach the database but can't create the objects Laravel needs in its target schema. The durable fix is to avoid "broad privileges everywhere" and instead isolate a dedicated test database, choose between ownership and explicit grants for that environment, and verify permissions at both the database and schema levels.

Then make PHPUnit and Laravel point to the exact same database/role. Clear cached config when needed, and use RefreshDatabase when your tests depend on database state. Start with a focused test to confirm setup works, then run the full suite and expand to real API behavior. If you're debugging this in your project, check the active connection and the public schema permissions first - those details often explain the failure before you touch application code.

Frequently Asked Questions

The PostgreSQL role Laravel uses can connect to the database but lacks the required permission to create objects in the public schema. Database access and schema permissions are separate. Check the active database and role, then verify schema ownership or grant the required schema privileges.
Not necessarily. Database ownership and schema ownership are separate. Changing the database owner does not automatically make that role the owner of public. Check both levels, or grant the test role the required schema privileges directly.
Yes, in most projects. Tests may run migrations and change database state, so a dedicated test database reduces the chance of damaging development data. It also makes the intended test connection and its permissions easier to verify.
RefreshDatabase prepares the database for tests using Laravel’s migration and transaction mechanisms as appropriate. It helps provide a predictable database state, but it does not fix incorrect credentials, a wrong database name, or missing PostgreSQL permissions.
Deprecation warnings can come from dependencies that use language features discouraged by the installed PHP version. They are separate from database errors. Check the PHP, Laravel, PHPUnit, and affected package versions, then plan compatible dependency updates.

Related articles