That was the moment I knew our integration tests were lying to us. Not in the obvious way—failing and telling us something was wrong—but in the worst possible way: passing, while our row-level security rules didn’t actually work.
Row-Level Security (RLS) lives deep inside the database. It decides which rows any given user can see or edit. A single mistake in those rules can mean leaking sensitive data or corrupting records. The hard truth is this: integration tests that don’t check your RLS in a production-like environment can produce a false sense of safety. And false safety is worse than none at all.
Why integration testing RLS fails in most setups
Many teams wire integration tests to run against a blank or in-memory database. It’s fast. It’s convenient. But it’s not your real schema, real permissions, or real RLS policies. Without the same grants, roles, and policies you deploy live, you’re not actually testing RLS—you’re testing a toy model that happens to share some table names.
What to test, and how
RLS testing has to prove that:
- The right users can see only the right rows.
- The wrong users see nothing or get rejected.
- All queries go through the exact roles, grants, and policies you ship.
That means running integration tests against a database where real RLS rules are enabled, using the same migrations and user roles you deploy. Tests should simulate actual connections with token-based or role-based authentication, not just swapping a variable in code.
Automating RLS checks in CI
The best setups spin up a real instance of your database—often in Docker—with RLS fully configured, migrations applied, and test data seeded with row ownership. Your integration tests then hit this instance exactly as your application would. This catches privilege escalations, missing policies, or regressions as soon as they appear in branch code.
Performance and maintenance
Yes, it’s slower than an in-memory database. Yes, it’s more work to keep RLS rules in sync. But for security-sensitive systems, nothing beats testing the real thing. And when it’s automated in your continuous integration pipeline, the safety net works without slowing your team’s momentum.
Security rules aren’t meant to be declared once and forgotten. They should be proven every time code changes touch the data layer. The cost of not doing this is only visible after it’s too late.
If you want to see RLS integration testing running in a production-like environment without spending weeks building it yourself, you can set it up and watch it work on hoop.dev in minutes.