1

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

1 Answer 1

1

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);
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Hello and thanks for answer :) I checked your code and i have error in line 26, it is $productRepository and error: Error: Call to a member function get() on null
@PawelC actually you can mock the repository as well productRepository = $this->createMock(ProductRepository::class); But then you won't be able to execute the last assert
I change this line, now i have problem with line 28 it is $entity manager, when i change this line to $this->createMock(EntityManager::class) the i have error PHP Fatal error: Class Mock_ValidatorInterface_b607e347 contains 6 abstract methods and must therefore be declared abstract
Now i have another mistake, check my first post. I think it's not problem with phpunit
@PawelC check that in Product entity annotation exists this line: @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.