而不是写作单元测试你应该写集成测试对于持久层。
单元测试有一条规则“不要嘲笑你不拥有的东西".
您不拥有 Doctrine 类或接口,并且您永远无法确定您对模拟的接口所做的假设是否正确。即使它们在您编写测试时是正确的,您也不能确定 Doctrine 的行为不会随着时间的推移而改变。
每当您使用第三方代码时,您都应该为使用它的任何内容编写集成测试。集成测试实际上会调用数据库并确保该原则按照您认为的方式工作。
这就是为什么最好decouple来自第 3 方的东西。
就教义而言,这很容易。您可以做的事情之一是为每个存储库引入一个界面。
interface ArticleRepository
{
/**
* @param int $id
*
* @return Article
*
* @throws ArticleNotFoundException
*/
public function findById($id);
}
您可以轻松模拟或存根这样的接口:
class MyServiceTest extends \PHPUnit_Framework_Test_Case
{
public function test_it_does_something()
{
$article = new Article();
$repository = $this->prophesize(ArticleRepository::class);
$repository->findById(13)->willReturn($article);
$myService = new MyService($repository->reveal());
$myService->doSomething();
// ...
}
}
该接口将按照以下原则实现:
use Doctrine\ORM\EntityRepository;
class DoctrineArticleRepository extends EntityRepository implement ArticleRepository
{
public function findById($id)
{
// call doctrine here
}
}
实现就是您要为其编写集成测试的内容。在存储库的集成测试中,您实际上将调用数据库:
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ArticleTest extends KernelTestCase
{
private $em;
private $repository;
protected function setUp()
{
self::bootKernel();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
$this->repository = $this->em->getRepository(Article::class);
}
public function test_it_finds_an_article()
{
$this->createArticle(13);
$article = $this->repository->findById(13);
$this->assertInstanceOf(Article::class, $article);
$this->assertSame(13, $article->getId());
}
/**
* @expectedException ArticleNotFoundException
*/
public function test_it_throws_an_exception_if_article_is_not_found()
{
$this->repository->findById(42);
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
}
private function createArticle($id)
{
$article = new Article($id);
$this->em->persist($article);
$this->em->flush();
// clear is important, otherwise you might get objects stored by doctrine in memory
$this->em->clear();
}
}
在其他地方,您将使用将被存根(或模拟)的接口。在其他任何地方你都不会访问数据库。这使您的测试速度更快。
存根中的实体可以简单地创建或存根。大多数时候我创建实体对象而不是模拟它们。