【问题标题】:[PHPUnit], [Symfony]: test that Entity was saved in DB[PHPUnit]、[Symfony]:测试实体是否保存在数据库中
【发布时间】:2019-11-19 19:36:09
【问题描述】:

我的测试有问题。我学习如何编写 phpunit 测试以及如何模拟对象、服务等。我的 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();
        }
    }
}

我写了这个测试:

<?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));
    }
}

当我运行 phpunit 测试时,我总是成功但我的数据库是空的。如何确保测试正常工作?什么值得修复,什么不值得?我想让测试的启动导致,例如,将记录添加到测试数据库,但我不知道如何去做以及如何正确地模拟它。我使用 phpunit + Symfony 4。

我曾经写过测试,但是那些要求端点 API 的,在这里我想测试没有端点的服务和存储库。

我想学习如何测试和模拟网站、存储库、各种类等。

当我应用答案时,我有:

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

我的产品实体

<?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;
    }
}

产品库

<?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

【问题讨论】:

    标签: unit-testing symfony mocking phpunit symfony4


    【解决方案1】:

    首先,当你模拟一个方法时,原始方法不再存在,在这个测试中。在您的情况下,您将 ProductService::createProduct 替换为以下内容:

    // This is your mock
    class ProductService 
    {
        // ...
    
        public function createProduct($productData)
        {
            return $this;
        }
    }
    

    您的测试没有检查任何内容。

    如果你想测试真正的功能那么

    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);
        }
    }
    

    【讨论】:

    • 您好,感谢您的回答 :) 我检查了您的代码,第 26 行有错误,它是 $productRepository 和错误:错误:在 null 上调用成员函数 get()
    • @PawelC 实际上你也可以模拟存储库productRepository = $this-&gt;createMock(ProductRepository::class);但是你将无法执行最后一个断言
    • 我更改了这一行,现在我遇到了第 28 行的问题,它是 $entity manager,当我将此行更改为 $this-&gt;createMock(EntityManager::class) 时出现错误 PHP Fatal error: Class Mock_ValidatorInterface_b607e347 contains 6 abstract methods and must therefore be declared abstract
    • 现在我又犯了一个错误,请查看我的第一篇文章。我认为phpunit没有问题
    • @PawelC 检查产品实体注释中是否存在这一行:@ORM\Entity(repositoryClass="App\Repository\ProductRepository")
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-01
    • 1970-01-01
    • 2022-11-17
    • 2016-05-12
    • 2011-05-08
    • 1970-01-01
    • 2018-03-18
    相关资源
    最近更新 更多