Pass an explicit request as the second argument of QueryBuilder::for() to test an allow-list without HTTP:
use Illuminate\Http\Request;
use RoundlyConsulting\QueryBuilder\QueryBuilder;
it('filters by status and sorts by views', function () {
Post::factory()->create(['title' => 'Alpha', 'status' => 'published', 'views' => 30]);
Post::factory()->create(['title' => 'Beta', 'status' => 'draft', 'views' => 10]);
Post::factory()->create(['title' => 'Gamma', 'status' => 'published', 'views' => 20]);
$titles = QueryBuilder::for(Post::class, Request::create('/?filter[status]=published&sort=-views'))
->allowedFilters('status')
->allowedSorts('views')
->get()
->pluck('title')
->all();
expect($titles)->toBe(['Alpha', 'Gamma']);
});Endpoint tests
Unknown keys and invalid page sizes surface as ordinary HTTP errors, so assert them on the endpoint (here the PostController from Building queries):
it('rejects a filter that is not allow-listed', function () {
$this->getJson('/posts?filter[secret]=1')
->assertStatus(400)
->assertJson([
'message' => 'Requested filter(s) `secret` are not allowed. Allowed filter(s) are `status, title`.',
]);
});
it('drops unknown filters in ignore mode', function () {
config()->set('query-builder.mode.unknown_filter', 'ignore');
$this->getJson('/posts?filter[secret]=1')->assertOk();
});
it('validates the page size', function () {
$this->getJson('/posts?per_page=500')->assertStatus(422);
});Run the suite against every database you deploy to — text matching uses ILIKE on PostgreSQL and LIKE elsewhere, case folding differs per engine, and PostgreSQL is where value shapes matter.
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 cryptoBy donating, you agree to our donation terms.
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.