Bernardo Borges
Kishido Dev Blog

Kishido Dev Blog

Unit Testing in a Symfony Project

Unit Testing in a Symfony Project

Subscribe to my newsletter and never miss my upcoming articles

This a topic I wanted to research for a while, I believe a good code base should have equally good unit testing. Maybe that error in your last production push could be avoided by a simple unit test. This is a my take In a big topic and for that, anyone with a tip and trick should comment below to help build a good discussion!

Recently I posted Build a bookshelf with Symfony , Where I created a simple CRUD Application using Symfony, let's use this project and now I'll add some unit testing to the interactions with the Sqlite.

You can clone this repo here !

Packages

Firstly we need to add the testing packege to the project

 composer require --dev phpunit/phpunit symfony/test-pack

Base Code

We're going to build 3 tests in consideration that we have a Controller( BookController.php) like this one.

class BookController extends AbstractController
{

    public function index(): Response
    {
        return $this->render('book/index.html.twig', [
            'controller_name' => 'BookController',
        ]);
    }


    public function new(Request $request): Response
    {
        $book = new Book();
        $book->setName('Book');
        $book->setMaxPages(1);
        $book->setPagesRead(0);

        $form = $this->createFormBuilder($book)
            ->add('name', TextType::class)
            ->add('max_pages', IntegerType::class)
            ->add('pages_read', IntegerType::class)
            ->add('save', SubmitType::class, ['label' => 'Create Book'])
            ->getForm();

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $bookData = $form->getData();
            $db = $this->getDoctrine()->getManager();
            $db->persist($bookData);
            $db->flush();
            return $this->redirectToRoute('listBook');
        }

        return $this->render('book/book.html.twig', [
            'form' => $form->createView(),
        ]);
    }


    public function list(): Response
    {
        $books = $this->getAll();
        return $this->render('book/list.html.twig', ['list' => $books]);
    }

    public function delete(int $id, Request $request): Response
    {
        $entityManager = $this->getDoctrine()->getManager();
        $book = $entityManager->getRepository(Book::class)->find($id);
        $entityManager->remove($book);
        $entityManager->flush();
        return $this->redirectToRoute('listBook');
    }


    public function edit(int $id, Request $request): Response
    {

        $book = $this->getDoctrine()
            ->getRepository(Book::class)
            ->find($id);

        $form = $this->createFormBuilder($book)
            ->add('name', TextType::class)
            ->add('max_pages', IntegerType::class)
            ->add('pages_read', IntegerType::class)
            ->add('save', SubmitType::class, ['label' => 'Edit Book'])
            ->getForm();

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $bookData = $form->getData();
            $db = $this->getDoctrine()->getManager();
            $db->persist($bookData);
            $db->flush();
            return $this->redirectToRoute('listBook');
        }

        return $this->render('book/book.html.twig', [
            'form' => $form->createView(),
        ]);
    }


    public function createBook(Book $book)
    {
        try {
            $db = $this->getDoctrine()->getManager();
            $db->persist($book);
            $db->flush();
            return true;
        } catch (Exception $ex) {
            throw new $ex;
        }
    }


    public function getAll()
    {
        $repository = $this->getDoctrine()->getRepository(Book::class);
        $books = $repository->findAll();
        return $books;
    }

}

and a Entity(Book.php) with this parameters

/**
 * @ORM\Entity(repositoryClass=BookRepository::class)
 */
class Book
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="integer")
     */
    private $max_pages;

    /**
     * @ORM\Column(type="integer", nullable=true)
     */
    private $pages_read;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getMaxPages(): ?int
    {
        return $this->max_pages;
    }

    public function setMaxPages(int $max_pages): self
    {
        $this->max_pages = $max_pages;

        return $this;
    }

    public function getPagesRead(): ?int
    {
        return $this->pages_read;
    }

    public function setPagesRead(?int $pages_read): self
    {
        $this->pages_read = $pages_read;

        return $this;
    }
}

Building the Tests

Having this in mind let's create a test for creating a Book Object , listing Books and create a Book.

In the Test folder create the BookTest.php file so we can write the tests.

Create Base Class

To create groups of tests we create a Class to house those particular tests, in this case since we're testing the Book controller we created the BookTest.php

<?php declare(strict_types=1);

use App\Controller\BookController;
use App\Entity\Book;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

final class BookTest extends KernelTestCase
{


}

?>

Test Creating a Book Object

  //test Creating a Book object
    public function testBuildBookObject(): void
    {
        //init object
        $book = new Book();
        // add base values
        $book->setName('Book Test');
        $book->setMaxPages(300);
        $book->setPagesRead(250);

        $this->assertInstanceOf(Book::class, $book); //Must retrun a Book object to succeed
    }

Running the Test should result this:

image.png

Test Inserting a Book in the Database

Now for the Insert into the database, we use the same function we use normally in the controller so you don't have to replicate the same code for the test and for the controller.

public function testCreate()
    {
        self::bootKernel();

        $container = self::$container;
        $book = new Book();
        $book->setName('Book Test');
        $book->setMaxPages(300);
        $book->setPagesRead(250);

        $bookController = $container->get(BookController::class);
        $this->assertEquals(true,$bookController->createBook($book));
    }

Test Listing Books

And finally for the book listing.

  public function testList()
    {
        self::bootKernel();
        $container = self::$container;
        $bookController = $container->get(BookController::class);
        $this->assertContainsOnlyInstancesOf('Book',$bookController->getAll());
    }

Run the Tests

With the test done we can now run them all and see the result.

./vendor/bin/phpunit --testdox tests

Result

Screenshot from 2021-06-19 12-04-33.png

Conclusion

We've created basic test for a specific class, a bigger project should have more tests, this is just a small tutorial about creating your first tests in a symfony project.

Go check the source code or comment below if you have any questions !

 
Share this