Functional and unit tests

May 10, 2023 MrAnyx 16 min de lecture
Intermédiaire

So far, we have basically spent our time developing and adding new features. However, to make our application more robust and simply to verify that our application works properly, we will have to test it.

Of course, we are not going to manually test our application by hand. Although it's a small api, we'll automate it instead.

Writing test files also allows us to ensure that our api works properly when we add new features. In other words, it guarantees the backward compatibility of the changes.

Several libraries exist to test a PHP program. We can mention Pest, Codeception or PhpSpec. Fortunately, Symfony already includes a tool to test our application: PHPUnit. This tool is particularly robust and is widely used, which means that in case of problem, you should easily find a solution.

Installing the library

As before, we will use composer to install the necessary libraries.

composer require --dev symfony/test-pack

This command allows us to install PHPUnit and other utility libraries in order to perform our tests.

In order to check that the installation works, let's run the command that allows us to execute our tests.

php bin/phpunit

If no errors are returned, all is good.

A file is considered a test file if it is located in the /tests folder and the class contained in that file ends with Test.

Test structure

To test our application properly, we will create different types of test files. As indicated in the documentation, we can distinguish them in 3 categories:

  • Unit tests: to test a single functionality
  • Integration tests: to test a succession of functionalities
  • Application (or functional) tests: to functionally test a feature via HTTP requests.

In our case, we will mainly create unit tests and application tests.

For example, we will create unit tests to check the getters and setters of our entities or to test our OptionsResolvers. On the other hand, we will also create application tests to check the correct functioning of our different urls.

Different types of classes

You will soon see that there are several types of test classes:

  • TestCase : For a class that does not need service injection
  • KernelTestCase : For a class that needs service injection
  • WebTestCase : For using the client to generate HTTP requests
  • ApiTestCase : For testing API Platform related features
  • PantherTestCase : To test a feature from end to end using a real browser.

Each type brings different functionalities according to the needs. Some will therefore be much more suitable than others.

Creating the first test file

Let's try to verify that our /api/todos route works properly. To start, we will have to create a test file with the following command.

php bin/console make:test

Next, we will choose the type WebTestCase. We don't choose the type ApiTestCase because for this type of test, we have to use API Platform, which is not our case.

We will then fill in the following name:

Controller\TodoControllerTest

In this way, the structure of the test files corresponds to the structure of the src folder.

You should have the following sequence of commands.

This command should have created a new TodoControllerTest file in the /tests/Controller directory with the following contents.

// tests/Controller/TodoControllerTest.php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class TodoControllerTest extends WebTestCase
{
    public function testSomething(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Hello World');
    }
}

We will slightly modify this test file because it does not allow to test correctly a REST API.

We will first modify the line

// tests/Controller/TodoControllerTest.php

$crawler = $client->request('GET', '/');

by replacing the url.

// tests/Controller/TodoControllerTest.php

$crawler = $client->request('GET', '/api/todos');

Then we will delete the line

// tests/Controller/TodoControllerTest.php

$this->assertSelectorTextContains('h1', 'Hello World');

because in the context of an api, it doesn't make sense.

We now have the following code.

// tests/Controller/TodoControllerTest.php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class TodoControllerTest extends WebTestCase
{
    public function testSomething(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/api/todos');

        $this->assertResponseIsSuccessful();
    }
}

Now let's run our test set with the command we saw earlier.

php bin/phpunit

Unfortunately, an error should appear.

This is simply because this route, like many others, needs a database to retrieve the todo elements.

Setting up the test database

When we installed the symfony/test-pack library at the beginning of the lesson, a .env.test file was also created at the root of the project. This file, like the .env or .env.local file, allows to set environment variables specific to the test environment.

In our case, we will use this file to define the DATABASE_URL variable.

At the end of the file, we will add the following line.

# .env.test

DATABASE_URL="mysql://db_user:db_password@db_host:db_port/db_name"

It is simply a copy of the line corresponding to the DATABASE_URL variable in the .env.local file.

It is not necessary to suffix the database name with a term like test because Symfony will take care of that. For example, if you fill in the name api as the database name, Symfony will create the database under the name api_test so as not to conflict with other existing databases.

Once done, it is enough to launch the commands allowing to create the base, create the structure and create false values.

php bin/console doctrine:database:create --env=test
php bin/console doctrine:migrations:migrate --env=test
php bin/console doctrine:fixtures:load --env=test

Once the commands are executed, you should see this new database appear on your database server.

Now let's run our test set again and see the result.

php bin/phpunit

No more errors are returned. This is perfect.

Creating application tests

Unfortunately, writing tests of any kind is particularly repetitive. So to save you this tedious step, I'll give you the code and we'll go into more detail later.

// tests/Controller/TodoControllerTest.php

namespace App\Tests\Controller;

use App\Entity\Todo;
use App\Entity\User;
use App\Repository\TodoRepository;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response;

class TodoControllerTest extends WebTestCase
{
    private TodoRepository $todoRepository;
    private UserRepository $userRepository;
    private KernelBrowser $client;

    /**
     * Initializing attributes
     */
    protected function setUp(): void
    {
        $this->client = static::createClient();

        $entityManager = self::getContainer()->get('doctrine')->getManager();
        $this->todoRepository = $entityManager->getRepository(Todo::class);
        $this->userRepository = $entityManager->getRepository(User::class);
    }

    /**
     * Test the format of a paginated response
     */
    private function testPaginatedResponseFormat(): void
    {
        // Retrieve the result of the response
        $response = $this->client->getResponse();
        $result = json_decode($response->getContent(), true);

        // Check the presence and the type of the "data" field
        $this->assertArrayHasKey("data", $result);
        $this->assertIsArray($result["data"]);

        // Check the format of each element within the "data" field
        foreach ($result["data"] as $todo) {
            $this->testTodoFormat($todo);
        }

        // Perform the same operations for the "pagination" field
        $this->assertArrayHasKey("pagination", $result);
        $this->assertIsArray($result["pagination"]);

        $paginationKeys = ["total", "count", "offset", "items_per_page", "total_pages", "current_page", "has_next_page", "has_previous_page", ];
        foreach ($paginationKeys as $key) {
            $this->assertArrayHasKey($key, $result["pagination"]);
        }
    }

    /**
     * Test the format of a todo element
     */
    private function testTodoFormat(array $todoAsArray): void
    {
        // Check the presence of each todo fields
        $todoKeys = ["id", "title", "createdAt", "updatedAt", "completed"];
        foreach ($todoKeys as $key) {
            $this->assertArrayHasKey($key, $todoAsArray);
        }
    }

    /**
     * Test the GET /api/todos route
     */
    public function testGetTodos(): void
    {
        // Make a request with default page parameter
        $this->client->request('GET', '/api/todos');

        // Check if the request is valid
        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_OK);
        $this->assertResponseFormatSame("json");

        // Check the response format
        $this->testPaginatedResponseFormat();

        // Perform the same operations with a custom page parameter
        $this->client->request('GET', '/api/todos?page=2');

        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_OK);
        $this->assertResponseFormatSame("json");

        $this->testPaginatedResponseFormat();

        // Perform the same operations with an invalid page parameter
        $this->client->request('GET', '/api/todos?page=hello');
        $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
        $this->client->request('GET', '/api/todos?page=-2');
        $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
    }

    /**
     * Test the GET /api/todos/{id} route
     */
    public function testGetTodo(): void
    {
        // Retrieve a todo from the database
        $todo = $this->todoRepository->findOneBy([]);

        // Make the request
        $this->client->request('GET', "/api/todos/{$todo->getId()}");

        // Check if it's successful
        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_OK);
        $this->assertResponseFormatSame("json");

        // Check the response format
        $response = $this->client->getResponse();
        $result = json_decode($response->getContent(), true);
        $this->testTodoFormat($result);
    }

    /**
     * Test the POST /api/todo route
     */
    public function testCreateTodo(): void
    {
        // Make the request with body paramater without the "X-AUTH-TOKEN" header to chech the security
        $this->client->request('POST', "/api/todos", content: json_encode(["title" => "new Todo"]));

        // Check if the response status code is "401 Unauthorized"
        $this->assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);

        // Retrieve a user from the database
        $user = $this->userRepository->findOneBy([]);

        // Make the request with the token header and the same body parameter
        $this->client->request(
            'POST',
            "/api/todos",
            server: [
                "HTTP_X_AUTH_TOKEN" => $user->getToken()
            ],
            content: json_encode(["title" => "new Todo"])
        );

        // Check if the response if successful
        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_CREATED);

        // Check the response format
        $response = $this->client->getResponse();
        $result = json_decode($response->getContent(), true);
        $this->testTodoFormat($result);

        $this->assertSame("new Todo", $result["title"]);
    }

    /**
     * Test the DELETE /api/todos/{id} route
     */
    public function testDeleteTodo(): void
    {
        // As for the previous method, we first make the request without the token header
        $todo = $this->todoRepository->findOneBy([]);
        $this->client->request('DELETE', "/api/todos/{$todo->getId()}");

        $this->assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);

        // Make the request with the token header
        $user = $this->userRepository->findOneBy([]);
        $this->client->request(
            'DELETE',
            "/api/todos/{$todo->getId()}",
            server: [
                "HTTP_X_AUTH_TOKEN" => $user->getToken()
            ],
        );

        // Check if the request is successful
        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_NO_CONTENT);
    }

    /**
     * Test the PATCH /api/todos/{id} route
     */
    public function testPartialUpdate(): void
    {
        $todo = $this->todoRepository->findOneBy([]);
        $this->client->request('PATCH', "/api/todos/{$todo->getId()}");

        $this->assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);

        $user = $this->userRepository->findOneBy([]);
        $this->client->request(
            'PATCH',
            "/api/todos/{$todo->getId()}",
            server: [
                "HTTP_X_AUTH_TOKEN" => $user->getToken()
            ],
            content: json_encode(["title" => "Updated title"])
        );

        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_OK);

        $response = $this->client->getResponse();
        $result = json_decode($response->getContent(), true);
        $this->testTodoFormat($result);

        $this->assertSame("Updated title", $result["title"]);
    }

    /**
    * Test the PUT /api/todos/{id} route
    */
    public function testFullUpdate(): void
    {
        $todo = $this->todoRepository->findOneBy([]);
        $this->client->request('PUT', "/api/todos/{$todo->getId()}");

        $this->assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);

        $user = $this->userRepository->findOneBy([]);

        // Missing parameter
        $this->client->request(
            'PUT',
            "/api/todos/{$todo->getId()}",
            server: [
                "HTTP_X_AUTH_TOKEN" => $user->getToken()
            ],
            content: json_encode(["title" => "Updated title"])
        );

        $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);

        // Valid request
        $this->client->request(
            'PUT',
            "/api/todos/{$todo->getId()}",
            server: [
                "HTTP_X_AUTH_TOKEN" => $user->getToken()
            ],
            content: json_encode(["title" => "Updated title", "completed" => true])
        );

        $this->assertResponseIsSuccessful();
        $this->assertResponseStatusCodeSame(Response::HTTP_OK);

        $response = $this->client->getResponse();
        $result = json_decode($response->getContent(), true);
        $this->testTodoFormat($result);

        $this->assertSame("Updated title", $result["title"]);
        $this->assertSame(true, $result["completed"]);
    }
}

The goal here is to test as many different things as possible in order to ensure the proper functioning of our api.

So we find several methods :

  • setUp
  • testPaginatedResponseFormat
  • testTodoFormat
  • testGetTodos
  • testGetTodo
  • testCreateTodo
  • TestDeleteTodo
  • TestPartialUpdate

Each of these methods has a specific purpose. We can still distinguish these methods in 2 categories:

  • Those which allow to configure the test class
  • Those which allow to test a functionality

In this case, the setUp method simply initializes the various attributes of our class. Its operation is similar to that of a constructor.

All the other methods allow to test a functionality.

The code is commented to make it easier to understand.

We can now proceed to the unit tests

Creating unit tests

Entities

As with the application tests, we will write the code and then explain it. So here is the unit test file for the Todo entity.

// tests/Entity/TodoTest.php

namespace App\Tests\Entity;

use App\Entity\Todo;
use DateTimeImmutable;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class TodoTest extends KernelTestCase
{
    private EntityManager $em;
    private ValidatorInterface  $validator;

    protected function setUp(): void
    {
        $this->em = self::getContainer()->get('doctrine')->getManager();
        $this->validator = self::getContainer()->get("validator");
    }

    public function testDefaultValues(): void
    {
        $todo = new Todo();

        // Test default values
        $this->assertNull($todo->getId());
        $this->assertNull($todo->getTitle());
        $this->assertNull($todo->getCreatedAt());
        $this->assertNull($todo->getUpdatedAt());
        $this->assertFalse($todo->isCompleted());
    }

    public function testTitle()
    {
        $todo = new Todo();

        // Test entity constraints
        /** @var ConstraintViolation[] $errors */
        $errors = $this->validator->validateProperty($todo, "title");
        $this->assertInstanceOf(NotBlank::class, $errors[0]->getConstraint());

        $todo->setTitle("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas");
        /** @var ConstraintViolation[] $errors */
        $errors = $this->validator->validateProperty($todo, "title");
        $this->assertInstanceOf(Length::class, $errors[0]->getConstraint());

        // Test the title setter and getter methods
        $title = 'Test Todo';
        $todo->setTitle($title);
        $this->assertEquals($title, $todo->getTitle());
    }

    public function testCompleted()
    {
        $todo = new Todo();

        // Test the completed setter and getter methods
        $todo->setCompleted(true);
        $this->assertTrue($todo->isCompleted());
    }

    public function testDoctrineEvents()
    {
        $todo = new Todo();

        // Persist the entity (not flush) in order to generate the createdAt and updatedAt fields
        $this->em->persist($todo);

        // Test the createdAt and updatedAt setter and getter methods
        $this->assertInstanceOf(DateTimeImmutable::class, $todo->getCreatedAt());
        $this->assertInstanceOf(DateTimeImmutable::class, $todo->getUpdatedAt());

        // Detch the entity to prevent tracking unused entity
        $this->em->detach($todo);
    }
}

Once again, we find the setUp method and a set of methods to test all the fields of the entity

The setUp method works in the same way as the one we presented earlier for the application tests.

We then test all the getters and setters in order to verify their correct operation.

Finally, in the testDoctrineEvents method, we persist the entity in order to check the functioning of the Doctrine events (PrePersist and PreUpdate).

For the User entity, we will apply exactly the same principle.

// tests/Entity/UserTest.php

namespace App\Tests\Entity;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class UserTest extends KernelTestCase
{
    public function testDefaultValues(): void
    {
        $user = new User();

        // Test the ID getter method
        $this->assertNull($user->getId());
    }

    public function testUsername()
    {
        $user = new User();

        // Test the username setter and getter methods
        $username = 'test_user';
        $user->setUsername($username);
        $this->assertEquals($username, $user->getUsername());
        $this->assertEquals($username, $user->getUserIdentifier());
    }

    public function testRoles()
    {
        $user = new User();

        // Test the roles setter and getter methods
        $roles = ['ROLE_ADMIN', 'ROLE_USER'];
        $user->setRoles($roles);
        $this->assertEquals($roles, $user->getRoles());
    }

    public function testPassword()
    {
        $user = new User();

        // Test the password setter and getter methods
        $password = 'test_password';
        $user->setPassword($password);
        $this->assertEquals($password, $user->getPassword());
    }

    public function testToken()
    {
        $user = new User();

        // Test the token setter and getter methods
        $token = 'test_token';
        $user->setToken($token);
        $this->assertEquals($token, $user->getToken());
    }
}

The difference with the Todo entity test class is that, for the User class, we extend the TestCase class and not the KernelTestCase class because we do not need to inject a service. The User entity test class only needs the User entity to work.

Models

As you may have noticed, writing unit tests to test entities is very often similar. Come on, testing a model is not that complicated either. It works in a similar way.

// tests/Model/PaginatorTest.php

namespace App\Tests\Model;

use App\Model\Paginator;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class PaginatorTest extends KernelTestCase
{
    private Paginator $paginator;

    public function setUp(): void
    {
        // Create the Query object
        $em = self::getContainer()->get('doctrine')->getManager();
        $query = $em->createQueryBuilder()
            ->select("t")
            ->from('App\Entity\Todo', 't')
            ->getQuery();

        // Create the Paginator object
        $this->paginator = new Paginator($query);
    }

    public function testTotal(): void
    {
        $this->assertIsInt($this->paginator->getTotal());
    }

    public function testData(): void
    {
        $this->assertIsArray($this->paginator->getData());
    }

    public function testCount(): void
    {
        $this->assertIsInt($this->paginator->getCount());
    }

    public function testTotalPages(): void
    {
        $this->assertIsInt($this->paginator->getTotalPages());
    }

    public function testCurrentPage(): void
    {
        $this->assertIsInt($this->paginator->getCurrentPage());
    }

    public function testOffset(): void
    {
        $this->assertIsInt($this->paginator->getOffset());
    }

    public function testItemsPerPage(): void
    {
        $this->assertIsInt($this->paginator->getItemsPerPage());
    }

    public function testHasNextPage(): void
    {
        $this->assertIsBool($this->paginator->hasNextPage());
    }

    public function testHasPreviousPage(): void
    {
        $this->assertIsBool($this->paginator->hasPreviousPage());
    }

    public function testIterator(): void
    {
        // Convert paginator to an array (it uses the getIterator)
        $arrayPaginator = $this->paginator->getIterator();
        $this->assertArrayHasKey("data", $arrayPaginator);
        $this->assertArrayHasKey("pagination", $arrayPaginator);

        $this->assertArrayHasKey("total", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("count", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("offset", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("items_per_page", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("total_pages", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("current_page", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("has_next_page", $arrayPaginator["pagination"]);
        $this->assertArrayHasKey("has_previous_page", $arrayPaginator["pagination"]);
    }
}

First, we initialize the variables needed to test our model. Then, as for the entities, we test each getters.

Now we have to test the two OptionsResolvers that we have created to validate the data sent by the user.

Options Resolver

Let's start with the PaginatorOptionsResolver. Here, the principle will be to test every aspect of our OptionsResolver such as mandatory fields, allowed values, normalization, ...

Here is the code for the PaginatorOptionsResolver.

// tests/OptionsResolver/PaginatorOptionsResolverTest.php

namespace App\Tests\OptionsResolver;

use App\OptionsResolver\PaginatorOptionsResolver;
use PHPUnit\Framework\TestCase;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;

class PaginatorOptionsResolverTest extends TestCase
{
    private PaginatorOptionsResolver $optionsResolver;

    public function setUp(): void
    {
        $this->optionsResolver = new PaginatorOptionsResolver();
    }

    public function testValidPage(): void
    {
        $params = [
            "page" => "2"
        ];

        $result = $this->optionsResolver
            ->configurePage()
            ->resolve($params);

        $this->assertEquals(2, $result["page"]);
    }

    public function testNegativePage(): void
    {
        $params = [
            "page" => "-2"
        ];

        $this->expectException(InvalidOptionsException::class);

        $this->optionsResolver
            ->configurePage()
            ->resolve($params);
    }

    public function testDefaultPage()
    {
        $params = [];

        $result = $this->optionsResolver
            ->configurePage()
            ->resolve($params);

        $this->assertEquals(1, $result["page"]);
    }

    public function testStringPage()
    {
        $params = [
            "page" => "Hello World!"
        ];

        $this->expectException(InvalidOptionsException::class);

        $this->optionsResolver
            ->configurePage()
            ->resolve($params);
    }
}

As you can see, we test every possibility:

  • With a valid value
  • With a negative value
  • Without the page parameter
  • With one that is not a number

This covers all aspects of our OptionsResolver.

Finally, concerning the TodoOptionsResolver, here is the code we got.

// tests/OptionsResolver/TodoOptionsResolverTest.php

namespace App\Tests\OptionsResolver;

use App\OptionsResolver\TodoOptionsResolver;
use PHPUnit\Framework\TestCase;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;

class TodoOptionsResolverTest extends TestCase
{
    private TodoOptionsResolver $optionsResolver;

    public function setUp(): void
    {
        $this->optionsResolver = new TodoOptionsResolver();
    }

    public function testRequiredTitle()
    {
        $params = [];

        $this->expectException(MissingOptionsException::class);

        $this->optionsResolver
            ->configureTitle(true)
            ->resolve($params);
    }

    public function testValidTitle()
    {
        $params = [
            "title" => "My Title"
        ];

        $result = $this->optionsResolver
            ->configureTitle(true)
            ->resolve($params);

        $this->assertEquals("My Title", $result["title"]);
    }

    public function testInvalidTitle()
    {
        $params = [
            "title" => 3
        ];

        $this->expectException(InvalidOptionsException::class);

        $this->optionsResolver
            ->configureTitle(true)
            ->resolve($params);
    }

    public function testRequiredCompleted()
    {
        $params = [];

        $this->expectException(MissingOptionsException::class);

        $this->optionsResolver
            ->configureCompleted(true)
            ->resolve($params);
    }

    public function testValidCompleted()
    {
        $params = [
            "completed" => true
        ];

        $result = $this->optionsResolver
            ->configureCompleted(true)
            ->resolve($params);

        $this->assertEquals(true, $result["completed"]);
    }

    public function testInvalidCompleted()
    {
        $params = [
            "completed" => "Hello World!"
        ];

        $this->expectException(InvalidOptionsException::class);

        $this->optionsResolver
            ->configureCompleted(true)
            ->resolve($params);
    }
}

As for the previous one, the goal is to test a maximum of possibilities to limit the appearance of problems when we add new features and make our API evolve.

Repository

To finish, we just have to test our repository TodoRepository. Indeed, it is the only repository that we have modified. So it is important to test our modifications.

// tests/Repository/TodoRepositoryTest.php

namespace App\Tests\Repository;

use App\Entity\Todo;
use App\Model\Paginator;
use App\Repository\TodoRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class TodoRepositoryTest extends KernelTestCase
{
    private TodoRepository $repository;

    public function setUp(): void
    {
        $em = self::getContainer()->get("doctrine")->getManager();
        $this->repository = $em->getRepository(Todo::class);
    }

    public function testFindAllWithPagination(): void
    {
        $result = $this->repository->findAllWithPagination(1);

        $this->assertInstanceOf(Paginator::class, $result);
        $this->assertEquals(1, $result->getCurrentPage());
    }
}

That's about all we have to do to test our API.

In summary

In this lesson, we have seen different ways to write tests in order to validate the good working of our API, especially thanks to the unit tests, integration tests and application tests.

We also discussed the different types of classes we could use depending on our needs (TestCase, KernelTestCase, ...).

We then continued by writing application tests by simulating HTTP requests in order to verify the returned results. Then, we took care of the different unit tests for the entities as well as for the different classes for which it is necessary to test functionalities.

In the next lesson, we will see how to deploy and put in production our API.

Cette œuvre est mise à disposition selon les termes de la licence Licence Creative Commons