Type to search columns, filters, options, and extensions.

↑↓ to navigate · Enter to open · Esc to close

Documentation

Testing Your Tables

Assert what your DataTables return, with a PHPUnit base class

DataTableTestCase lets you assert what a table returns without writing a single DataTables query parameter. It ships with the bundle, in the Pentiminax\UX\DataTables\Test namespace.

There are two helpers, one per kind of table:

  • dataTable() sends a real request to the bundle’s Ajax route. Use it for a server-side table: the router, your firewall, the query pipeline, and your data provider all take part.
  • dataTableView() reads a table out of a page your application rendered. Use it for a client-side table, whose rows travel inside the page.

Requirements

The harness builds on Symfony’s functional testing tools, which live in your application’s development dependencies:

composer require --dev symfony/test-pack

That installs PHPUnit, symfony/browser-kit, symfony/dom-crawler, and symfony/css-selector. Nothing else is needed: the harness is part of the bundle you already require.

Testing a Server-Side Table

// tests/DataTable/BookDataTableTest.php
namespace App\Tests\DataTable;

use App\DataTable\BookDataTable;
use Pentiminax\UX\DataTables\Test\DataTableTestCase;

final class BookDataTableTest extends DataTableTestCase
{
    public function testSearchNarrowsTheResultSet(): void
    {
        static::createClient();

        $this->dataTable(BookDataTable::class)
            ->search('Symfony')
            ->orderBy('title', 'desc')
            ->page(1)
            ->length(10)
            ->fetch()
            ->assertRecordsTotal(120)
            ->assertRecordsFiltered(3)
            ->assertColumnValues('title', ['UX in Action', 'Symfony 7', 'Doctrine Deep Dive']);
    }
}

static::createClient() must run first — the builder uses that client. The table token and the columns[…] payload are derived from BookDataTable itself, so renaming a column breaks the test with a message naming the valid columns instead of silently changing what is asserted.

Building the request

Every method returns a new builder, so you can prepare one and branch it per assertion.

MethodPurpose
page(int $page)1-indexed page, resolved against the current length
start(int $start) / length(int $length)raw DataTables paging values
draw(int $draw)the draw counter, 1 by default
search(string $value, bool $regex = false)global search
orderBy(string $column, string $direction = 'asc')repeatable; takes a column name, not an index
filter(string $name, mixed $value)one value of a configured filter
columns(array $columns)replaces the derived column payload
query(array $parameters)extra query parameters, merged last

Reading the response

fetch() returns a DataTableResponse:

$response = $this->dataTable(BookDataTable::class)->fetch();

$response->draw();             // 1
$response->recordsTotal();     // rows before filtering
$response->recordsFiltered();  // rows after filtering
$response->rows();             // list of mapped rows
$response->row(0);             // one row
$response->raw();              // the decoded payload

The assertions chain and each returns the response:

$response
    ->assertRowCount(10)
    ->assertRecordsTotal(120)
    ->assertRecordsFiltered(3)
    ->assertRowsContain(['title' => 'Symfony 7'])
    ->assertColumnValues('title', ['Symfony 7', 'UX in Action']);

assertRowsContain() passes when one row carries every key and value you list. fetch() itself fails the test when the route answers anything other than a 200 JSON response, reporting the status code and the start of the body.

Testing a Client-Side Table

A client-side table has no Ajax route: its rows are embedded in the page that renders it. Request that page, then read the table from the crawler.

// tests/Controller/BookPageTest.php
namespace App\Tests\Controller;

use Pentiminax\UX\DataTables\Test\DataTableTestCase;

final class BookPageTest extends DataTableTestCase
{
    public function testThePageEmbedsEveryBook(): void
    {
        $client  = static::createClient();
        $crawler = $client->request('GET', '/books');

        $this->dataTableView($crawler)
            ->assertRowCount(12)
            ->assertColumns(['title', 'author', 'publishedAt'])
            ->assertColumnValues('title', ['Symfony 7', 'UX in Action'])
            ->assertOption('pageLength', 25);
    }
}

DataTableView also exposes rows(), row(int $index), columns(), option(string $name), and raw().

When the page renders more than one table, name the one you mean:

use App\DataTable\BookDataTable;

$this->dataTableView($crawler, BookDataTable::class)->assertRowCount(12);

Without that argument, a page carrying several tables fails with the number found.

Tables Behind a Firewall

The bundle’s Ajax routes are protected by your application’s security configuration, so a test authenticates the same way any other functional test does:

$client = static::createClient();
$client->loginUser($this->getEditor());

$this->dataTable(BookDataTable::class)->fetch()->assertRowCount(10);

To assert a denial, use fetchRaw(), which returns the untouched response instead of failing on a non-200:

static::createClient();

$response = $this->dataTable(BookDataTable::class)->fetchRaw();

$this->assertSame(403, $response->getStatusCode());

Fixtures

The harness asserts on whatever your provider returns or your controller passes to setData(). Seeding the database, or stubbing the provider, stays your application’s job — use the fixtures approach you already use for other functional tests.

What This Does Not Cover

DataTables.net never runs in a PHPUnit test. Client-side sorting, searching, and paging are browser behavior, so they are not exercised here: what these tests prove is that the right rows, columns, and options leave PHP. Cover the browser side with end-to-end tests instead.

Common Problems

SymptomCause
DataTable "…" is not registered.The class has no #[AsDataTable] attribute and is not registered as a service.
Unknown column "…". Available columns: …orderBy() received a column name the table does not declare.
returned HTTP 302 in a fetch() failureThe request was redirected to the login form — authenticate the client first.
Expected exactly one DataTable in the page, found 2.Pass the table class as the second argument to dataTableView().
A client-side assertion sees no rowsThe table is server-side; its rows come from the Ajax route, so use dataTable().