PawelC
PawelC

Reputation: 1226

[PHPUnit], [Symfony]: test that Entity was saved in DB

I have problem with my test. I learn how to write phpunit test and how i can mock object, services etc.. I have this method on my ProductService:

<?php

namespace App\Service;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ProductService
{
    /**
     * @var ProductRepository
     */
    private $productRepository;
    /**
     * @var EntityManager
     */
    private $entityManager;
    /**
     * @var ValidatorInterface
     */
    private $validator;

    /**
     * ProductService constructor.
     * @param ProductRepository $productRepository
     * @param EntityManagerInterface $entityManager
     * @param ValidatorInterface $validator
     */
    public function __construct(ProductRepository $productRepository, EntityManagerInterface $entityManager, ValidatorInterface $validator)
    {
        $this->productRepository = $productRepository;
        $this->entityManager = $entityManager;
        $this->validator = $validator;
    }

    /**
     * @param $productData
     * @return Product|string
     */
    public function createProduct($productData)
    {
        $name = $productData['name'];
        $quantity = $productData['quantity'];
        $sku = $productData['sku'];

        $product = new Product();
        $product->setName($name);
        $product->setQuantity($quantity);
        $product->setProductSerial($sku);

        $errors = $this->validator->validate($product);

        if (count($errors) > 0) {
            $errorsString = (string)$errors;

            return $errorsString;
        }

        try {
            $this->entityManager->persist($product);
            $this->entityManager->flush();
            return $product;
        } catch (\Exception $ex) {
            return $ex->getMessage();
        }
    }
}

and i write this test:

<?php

namespace App\Tests\Service;

use App\Entity\Product;
use App\Repository\ProductRepository;
use App\Service\ProductService;
use Doctrine\Common\Persistence\ObjectRepository;
use PHPUnit\Framework\TestCase;

class ProductServiceTest extends TestCase
{
    /**
     * Create product test
     */
    public function testCreateProduct()
    {
        $product = new Product();
        $product->setName('tester');
        $product->setQuantity(2);
        $product->setProductSerial('Examplecode');

        $productService = $this->createMock(ProductService::class);
        $productService->method('createProduct')->will($this->returnSelf());
        $this->assertSame($productService, $productService->createProduct($product));
    }
}

When i run phpunit test, then i always have success but my database is empty. How can I be sure that the test works correctly? What is worth fixing and what is not? I wanted to make the launch of tests result in, for example, adding records to the test database, but I have no idea how to do it and how to properly mock it. I using phpunit + Symfony 4.

I used to write tests, but those that asked the endpoint API, and here I want to test services and repositories without endpoints.

I would like to learn how to test and mock websites, repositories, various classes etc.

When i apply answer then i have:

PHPUnit 7.5.17 by Sebastian Bergmann and contributors.

Testing Project Test Suite
?[31;1mE?[0m                                                                   1 / 1 (100%)

Time: 542 ms, Memory: 10.00 MB

There was 1 error:

1) App\Tests\Service\ProductServiceTest::testCreateProduct
Doctrine\Common\Persistence\Mapping\MappingException: The class 'App\Repository\ProductRepository' was not found in the chain configured namespaces App\Entity, Gesdinet\JWTRefreshTokenBundle\Entity

D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\MappingException.php:22
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain.php:87
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:151
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:304
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:78
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:183
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:283
D:\warehouse-management-api\vendor\doctrine\doctrine-bundle\Repository\ContainerRepositoryFactory.php:44
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:713
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\AbstractManagerRegistry.php:215
D:\warehouse-management-api\tests\Service\ProductServiceTest.php:28

?[37;41mERRORS!?[0m
?[37;41mTests: 1?[0m?[37;41m, Assertions: 0?[0m?[37;41m, Errors: 1?[0m?[37;41m.?[0m

My Product entity

<?php

namespace App\Entity;

use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
 */
class Product
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

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

    /**
     * @ORM\Column(type="integer")
     * @Assert\NotBlank()
     */
    private $quantity;

    /**
     * @Gedmo\Mapping\Annotation\Timestampable(on="create")
     * @ORM\Column(type="datetime")
     */
    private $createdAt;

    /**
     * @Gedmo\Mapping\Annotation\Timestampable(on="update")
     * @ORM\Column(type="datetime")
     */
    private $updatedAt;

    /**
     * @ORM\Column(type="string")
     * @Assert\NotBlank()
     */
    private $product_serial;


    public function __construct() {
        $this->setCreatedAt(new \DateTime());
        $this->setUpdatedAt();
    }

    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 getQuantity(): ?int
    {
        return $this->quantity;
    }

    public function setQuantity(int $quantity): self
    {
        $this->quantity = $quantity;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    public function getUpdatedAt(): ?\DateTimeInterface
    {
        return $this->updatedAt;
    }

    public function setUpdatedAt(): self
    {
        $this->updatedAt = new \DateTime();

        return $this;
    }

    public function getProductSerial(): ?string
    {
        return $this->product_serial;
    }

    public function setProductSerial(string $product_serial): self
    {
        $this->product_serial = $product_serial;

        return $this;
    }
}

ProductRepository

<?php

namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;

class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }
}

doctrine.yaml

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci

        url: '%env(resolve:DATABASE_URL)%'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

Upvotes: 1

Views: 3311

Answers (1)

staskrak
staskrak

Reputation: 863

First of all, when you mock a method the original method doesn't exist any more, in this test. In your case you substitute ProductService::createProduct with something like this:

// This is your mock
class ProductService 
{
    // ...

    public function createProduct($productData)
    {
        return $this;
    }
}

Your test doesn't check anything.

If you want to test the real functionality then

namespace App\Tests\Service;

use App\Repository\ProductRepository;
use App\Service\ProductService;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ProductServiceTest extends KernelTestCase
{
    /**
     * Create product test
     */
    public function testCreateProduct(): void
    {
        // We load the kernel here (and $container)
        self::bootKernel();

        $productData = [
            'name' => 'foo',
            'quantity' => 1,
            'sku' => 'bar',
        ];
        $productRepository = static::$container->get('doctrine')->getRepository(ProductRepository::class);
        $entityManager = static::$container->get('doctrine')->getManager();

        // Here we mock the validator.
        $validator = $this->getMockBuilder(ValidatorInterface::class)
            ->disableOriginalConstructor()
            ->setMethods(['validate'])
            ->getMock();

        $validator->expects($this->once())
            ->method('validate')
            ->willReturn(null);

        $productService = new ProductService($productRepository, $entityManager, $validator);

        $productFromMethod = $productService->createProduct($productData);

        // Here is you assertions:
        $this->assertSame($productData['name'], $productFromMethod->getName());
        $this->assertSame($productData['quantity'], $productFromMethod->getQuantity());
        $this->assertSame($productData['sku'], $productFromMethod->getSku());

        $productFromDB = $productRepository->findOneBy(['name' => $productData['name']]);

        // Here we test that product in DB and returned product are same
        $this->assertSame($productFromDB, $productFromMethod);
    }
}

Upvotes: 1

Related Questions