Search⌘ K
AI Features

Project Challenge: Solution

Explore the complete solution for automated testing in a PHP web project, focusing on authentication, task management, access control, and validation. Understand how tests assert correct redirections, task operations, and user permissions using PHPUnit and Panther.

We'll cover the following...

Solution

Here is the complete implementation of the challenge. Check it out!

<?php

use PHPUnit\Framework\TestCase;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Panther\PantherTestCaseTrait;
use Symfony\Component\BrowserKit\HttpBrowser;

final class TasksTest extends TestCase
{
    use PantherTestCaseTrait;

    private HttpBrowser $client;

    // Helper function to check if current user has a task
    private function currentUserHasTask(string $task): bool
    {   
        // Make a request for the /list-tasks page
        $response = $this->client->request('GET', '/list-tasks');
        // Check if there is a task
        return strpos($response->filter('ul.tasks')->text(), $task) !== false;
    }

    protected function setUp(): void
    {
        $tasksJsonFile = __DIR__ . '/../data/tasks.json';
        if (is_file($tasksJsonFile)) {
            unlink($tasksJsonFile);
        }

        $this->client = self::createHttpBrowserClient(
            [
                'webServerDir' => __DIR__ . '/../public/'
            ]
        );
    }

    protected function tearDown(): void
    {
        $this->client->request('GET', '/logout');
    }

    /**
     * @test
     * @dataProvider securePaths
     */
    public function you_need_to_be_logged_in(string $path): void
    {
        // Fetching URLs
        $response = $this->client->request('GET', self::$baseUri . $path);
        // Check if redirected to /login page
        self::assertStringContainsString('/login', $response->getUri(), 'Expected to be redirected to the login page');
    }

    public function securePaths(): Generator
    {
        yield ['/list-tasks'];
        yield ['/create-task'];
    }

    /**
     * @test
     */
    public function after_logging_in_you_have_access_to_the_list_of_tasks(): void
    {
        // Login
        $this->login();
        // Redirecting to /list-tasks page
        $response = $this->client->request('GET', self::$baseUri . '/list-tasks');
        // Check if the page consists the right string
        self::assertStringContainsString('Tasks', $response->text());
    }

    /**
     * @test
     */
    public function you_can_create_a_task(): void
    {
        // Login
        $this->login();
        // Create a task
        $task = 'Build some Lego';

        $this->createTask($task);
        // Check if the task has been created
        self::assertTrue($this->currentUserHasTask($task));
    }

    /**
     * @test
     */
    public function you_can_edit_a_task(): void
    {
        // Login
        $this->login();
        // Create a Task
        $task = 'Build some Lego';
        $this->createTask($task);
        // Clicking edit button
        $response = $this->client->clickLink('Edit');
        // Check if the obtained task is equal to expected task
        self::assertEquals($task, $response->filter('input#task')->attr('value'));
        // Edit task
        $newTask = 'Buy some Lego';

        $this->client->submitForm(
            'Create',
            [
                'task' => $newTask
            ]
        );
        // Check if the task has been edited
        self::assertTrue($this->currentUserHasTask($newTask));
    }

    /**
     * @test
     */
    public function you_can_not_provide_an_empty_string_as_a_task(): void
    {
        // Login
        $this->login();
        // Create an empty string as a task
        $response = $this->createTask('');
        // Check if the right error message shows up
        self::assertStringContainsString(
            'Task can not be empty',
            $response->filter('strong')->text()
        );
    }

    /**
     * @test
     */
    public function you_can_not_see_other_tasks_of_other_users(): void
    {
        // Login as matthias
        $this->loginAs('matthias');
        // Create a task
        $task = 'Build some Lego';
        $this->createTask($task);
        // Login as tomas
        $this->loginAs('tomas');
        // Check if tomas can not see the task created by matthias
        self::assertFalse($this->currentUserHasTask($task));
    }

    /**
     * @test
     */
    public function you_can_not_edit_the_task_of_another_user(): void
    {
        // Login as matthias
        $this->loginAs('matthias');
        // Create a task
        $task = 'Build some Lego';
        $this->createTask($task);
        // Login as tomas
        $this->loginAs('tomas');
        
        // Task 1 will be owned by matthias, but tomas is the logged in user
        $response = $this->client->request('GET', self::$baseUri . '/edit-task?id=1');
        // Check if the right error is generated
        self::assertStringContainsString('You can not edit a task created by another user', $response->text());
    }

    /**
     * @test
     */
    public function you_can_mark_a_task_as_done_and_it_disappears_from_the_list(): void
    {
        // Log in as matthias
        $this->loginAs('matthias');
        // Create a task
        $task = 'Build some Lego';
        $this->createTask($task);
        // Click Done
        $this->client->submitForm('Done');
        // Check if the user has no task
        self::assertFalse($this->currentUserHasTask($task));
    }

    /**
     * @test
     */
    public function you_can_not_mark_the_task_of_another_user_as_done(): void
    {
        // Log in as matthias
        $this->loginAs('matthias');
        // Create a task
        $task = 'Build some Lego';
        $this->createTask($task);
        // Log in as tomas
        $this->loginAs('tomas');

        // Task 1 will be owned by matthias, but tomas is the logged in user
        $response = $this->client->request('POST', self::$baseUri . '/mark-as-done', ['id' => '1']);
        // Check if the right error message is generated
        self::assertStringContainsString('You can not mark a task created by another user as done', $response->text());
        // Log in as matthias
        $this->loginAs('matthias');
        // Check if the task is still there 
        self::assertTrue($this->currentUserHasTask($task));
    }

    // Helper functions to log in
    private function login(): void
    {
        $this->loginAs('matthias');
    }

    private function loginAs(string $username): void
    {
        $this->client->request('GET', self::$baseUri . '/login');

        $this->client->submitForm(
            'Submit',
            [
                'username' => $username,
                'password' => 'test'
            ]
        );
    }

    // Helper function to create task
    private function createTask(string $task): Crawler
    {
        $this->client->request('GET', self::$baseUri . '/create-task');

        return $this->client->submitForm(
            'Create',
            [
                'task' => $task
            ]
        );
    }
}

Explanation

Let’s have a look at the solution in detail.

tests/TasksTest.php file

Tests 1 & 2: You need to be logged in.

  • At line 49, we made requests for the /list-task and /create-task pages.
  • At line 51, we asserted if the unauthenticated user is redirected to the /login page.

Test 3: You get redirected to the list of tasks after logging in.

  • At line 66, we called the login function.
  • At line 68, we request the /list-tasks page.
  • At line 70, we
...