LinKeeper - Lesson 03 - Tests and ressources
Published: 2024-04-28 08:56:38

In this episode, we're going to take FilamentPHP a step further.

But first, we're going to set up our testing environment.

To do this, we're going to use PestPHP. PestPHP is already present because it was installed at the same time as Laravel.

To get to grips with Pest, we're going to create two very simple tests. Once we have the basics down, we can develop the rest of our application based on the tests.

What we'll be looking at today : 

{%- # TOC start (generated with https://github.com/derlin/bitdowntoc) -%}

- [Prior configuration of Pest](#prior-configuration-of-pest)
   * [Using SQLite for testing](#using-sqlite-for-testing)
   * [Refresh the database for each test](#refresh-the-database-for-each-test)
   * [Load a default account before each test](#load-a-default-account-before-each-test)
   * [Remove the tests provided by default by PestPHP](#remove-the-tests-provided-by-default-by-pestphp)
- [Checking the administration home page](#checking-the-administration-home-page)
   * [Running the test](#running-the-test)
   * [Correct the 403 error](#correct-the-403-error)
   * [Run the test again](#run-the-test-again)
- [Testing the user profile](#testing-the-user-profile)
   * [Run the tests](#run-the-tests)
- [a user can see the list of links associated with them](#a-user-can-see-the-list-of-links-associated-with-them)
   * [Let's create our tests for the Links page](#lets-create-our-tests-for-the-links-page)
   * [Let's create the Links page](#lets-create-the-links-page)
- [Conclusion ](#conclusion)

{%- # TOC end -%}


## Prior configuration of Pest

### Using SQLite for testing

In order to separate your application's database from the one used for testing, we're going to modify the `phpunit.xml` file located at the root of your project.

Simply uncomment the following two lines: 

```xml
        <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <env name="DB_DATABASE" value=":memory:"/> -->
```

```xml
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
```

This will allow Pest, which is a PHPUnit overlay, to use SQLite as its database engine and use it directly in memory.

### Refresh the database for each test

### Load a default account before each test

We're going to be doing a lot of work with FilamentPHP administration. So to avoid repeating for each test that we want to connect with a registered user, we'll simply tell Pest that for each test, it will act as if it were connected with the user with id #1. 

To make this user ex

To do this, we'll edit the `tests\TestCase.php` file and add the following `setUp` function:


```php 

    protected function setUp(): void
    {
        parent::setUp();

        $this->actingAs(User::find(1));
    }

```

So before each test is run, the `actingAs` function will define the user with id #1 as the logged-in user. Here it's my HappyToDev account.

### Remove the tests provided by default by PestPHP

When you install Pest, it provides you with two default tests: 

- tests\Feature\ExampleTest.php
- tests\Unit\ExampleTest.php

You can delete them or keep them for inspiration.



## Checking the administration home page

In this test, we'll go to the `/admin` page to check that

- an HTTP 200 code is received
- we can see 'Dashboard' on the page
- we can see the name of our application 'LinKeeper' on the page

We'll start by creating a test file using Artisan.

```bash
php artisan pest:test GeneralAdminPageTest
```

By default, the tests created will be stored directly in the `tests\Feature` directory.

Here are the contents of the file : 



```php 
<?php

use Illuminate\Support\Facades\Config;

it('can view admin page', function () {
    $response = $this->get('/admin');

    $response
        ->assertStatus(200)
        ->assertSee('Dashboard')
        ->assertSee(Config('APP_NAME'));
});
``` 

Explanation: 

We ask for get access to the `/admin` page and we get the response.

In this response, we check that we do indeed have an HTTP 200 code, that in the HTML code returned we do indeed have the word `Dashboard` and that we do indeed see the name of the application defined in our `.env` file, in this case `LinKeeper`. To do this, use the `config()` helper.

### Running the test

To run the test, at the root of your project, run the following command:

```bash
./vendor/bin/pest
``` 

Normally you should get a 403 error, so your test will not pass.

![Error 403](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4avad67wcaybgdjwn0lg.png)

### Correct the 403 error

To correct this, you need to read the [FilamentPHP documentation](https://filamentphp.com/docs/3.x/panels/installation#allowing-users-to-access-a-panel) which indicates that when you go into production, you need to modify the `User` model to avoid hitting a wall with the 403 error.

I'll leave it to you to implement the necessary changes so that your test can pass.

If you encounter the slightest difficulty, let me know in the comments.

### Run the test again

If you have correctly made the changes requested in the documentation, you should get this result when you run your tests.

![Test OK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ipkk9zxo1sgd6dugy4o.png)

## Testing the user profile

We saw in lesson 2 that we normally have access to the user's profile. Let's set up a test to check this.

```bash
php artisan pest:test UserProfileTest
```

and add the following code: 

```php
it('has user profile page', function () {
    actingAs(User::find(1))->get('/admin/profile')
    ->assertStatus(200)
    ->assertSee('Profile')
    ->assertSee('Save changes');
});
```

This will allow us to check that when I'm logged in with the user with id #1 (my user) I do indeed get an HTTP 200 code when I go to the `/admin/profile` page and I can see the words: 'Profile' and 'Save changes'.

This is a very simple test, but it validates that the user does indeed have access to this page.

### Run the tests

As before, run the appropriate command in your terminal: 

```bash
./vendor/bin/pest
``` 

and you should get the following result:


![Tests OK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0n9xpuc5g3c2gicip9y.png)


Now that we've taken our first steps with tests, we'll be able to take a test-driven development approach using PestPHP throughout this project.

## a user can see the list of links associated with them

This is the next stage in the development of our application.

As we are using FilamentPHP for the backend, a number of features are being developed by the FilamentPHP teams and the aim here will not be to test how well Filament works as I have no doubt that this has been tested extensively.

To view the list of links, the user will have a `Links` link in the dashboard menu and this link, by FilamentPHP convention, will lead to an `admin/links` address.

### Let's create our tests for the Links page

Let's create a new test, using artisan :

```bash
php artisan pest:test PageLinksTest
```

First of all, we want to make sure that the page is accessible.

To do this, we're going to add the following code to our `PageLinksTest` file: 

```php 
it("can access the user's links page and see 'Links' on the page", function () {
    $response = $this->get('/admin/links');

    $response
        ->assertOk()
        ->assertSeeText('Links');
});
```

This code will ask PestPHP to load the `/admin/links` page and to check firstly that the page returns the HTTP code 200 and then to ensure that the word 'Links' is present in the page.

You can run the tests with the `--filter` option to execute only the test that matches the regex passed in parameter ([see doc](https://pestphp.com/docs/filtering-tests#content---filter)). For example with : 

`./vendor/bin/pest --filter "links page"`

PestPHP will only run our last test because 'links page' is present in its description.

This avoids having to run all the tests each time, especially when you have to iterate several times when setting up your test.


![Tests KO](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2uzr0c2tly5d7g2z5o3w.png)

The test fails and that's normal because the Links page doesn't exist at the moment.

But now that the test is here and has failed, it tells us exactly what to do: 

> Expected response status code [200] but received 404.

We were expecting an HTTP 200 code and instead we get a 404, which means that the requested page doesn't exist. 

We simply need to create it.


### Let's create the Links page

The 'Links' page will be a FilamentPHP administration page. It will therefore be a resource.

If I take the definition from the tutorial provided with PestPHP : 

> In Filament, resources are static classes used to build CRUD interfaces for your Eloquent models. They describe how administrators can interact with data from your panel using tables and forms.


We therefore understand that each Eloquent model will be associated with a resource in FilamentPHP.

It is therefore time to create our resource associated with our Link model, which is itself linked to our 'links' table in our database.

```bash
php artisan make:filament-resource Link
```

In our application tree, under `app/Filament/Resources`, we can see a `LinkResource` directory and a `LinkResource.php` file. 


![Arborescence Filament Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0e2r4l594titrnwehto.png)

The `LinkResource` directory itself has a `Pages` sub-directory which contains the `CreateLink.php`, `EditLink.php` and `ListLinks.php` files. We'll talk about these later in this tutorial, so don't worry about these extra files for now.

FilamentPHP, via this Artisan command, will generate the following skeleton for you:

```php
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\LinkResource\Pages;
use App\Filament\Resources\LinkResource\RelationManagers;
use App\Models\Link;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;

class LinkResource extends Resource
{
    protected static ?string $model = Link::class;

    protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                //
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                //
            ])
            ->filters([
                //
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getRelations(): array
    {
        return [
            //
        ];
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListLinks::route('/'),
            'create' => Pages\CreateLink::route('/create'),
            'edit' => Pages\EditLink::route('/{record}/edit'),
        ];
    }
}
``` 

Let's not worry about that for the moment. But let's run our test again: 

```bash
./vendor/bin/pest --filter "links page"
```

We can see that this time our test is green.


![Tests OK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xb44qt2080ztinc3ws8.png)

If we go to the `admin/links` page, we can see that the page does exist (HTTP code 200) and that it does indeed say **'Links'**. This is why our test turns green.


![Page admin/links](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w63ujncufjdh05d2io2y.png)

However, we soon realise that something isn't quite right. There seem to be records but nothing is visible in the table that shows us the links.

This is normal, we'll need to configure our resource file.



## Conclusion 

We'll stop here for episode 3.

In this chapter, we started doing TDD for our application development and touched on resources in FilamentPHP.

In the next episode, we'll continue our testing strategy with PestPHP and get to know FilamentPHP's resources better.

As usual, I look forward to your comments below.

See you soon.