我有一个AuthorizationUpdater
服务,其实现基于Doctrine ORM。当我完成生产代码并进行一些测试时,由于不知道如何编写这两个代码,我被迫退出了通常的TDD周期:
- testWhenDatabaseReadFails_exceptionIsThrown(测试此代码)
- testWhenDatabaseWriteFails_exceptionIsThrown(测试此代码)
AuthorizationUpdater
的 Doctrine 实现在其构造函数中采用 Doctine EntityManager
,它用于执行读取和写入。这两个测试应该测试当这些 EntityManager 方法中的任何一个抛出 Doctine ORMException
时,它会被服务捕获并转换为域异常。
我的测试是一个集成测试,它从工厂获得EntityManager
使用。
在这种情况下,通过PHPUnit模拟API(getMock('className')->method('updateStuff')->willThrow(new SomeExcetion)
)创建一个简单的假货似乎并不好。该服务在抛出异常的方法之前使用该EntityManager
,因此无法模拟所有内容。这留下了一些复杂的EntityManager
伪造并部分模拟它(只是应该引发异常的方法)。后者需要提供真正的构造函数参数,我的测试实际上无法访问这些参数,因为此责任在另一个包中。因此,这两种方法都不令人满意,让我宁愿不测试这种ORMException
捕获行为。
有没有一种好方法可以编写我想要的测试?能够在EntityManager
实例上对相关方法进行猴子修补将是完美的,尽管这需要 runkit 扩展。
如果需要,也许可以切换到 PHP 7.1。如果您好奇,代码在 GitHub 上,相关测试驻留在 DoctrineAuthorizationUpdaterTest
的末尾。
与其通过 PHPUnit 模拟 API 创建模拟对象,不如在测试命名空间中创建一个代理类,该类从工厂获取EntityManager
作为构造函数参数。代理类将每个方法调用传递给EntityManager
,除了要抛出异常的调用。大多数调用都可以使用 __call
magic 方法传递,除了对 Doctrine'Common'Persistence'ObjectManager
接口方法的调用,Proxy 类可能必须实现(接口方法不能用 call
实现)。
这个代理类是你正在谈论的"复杂的假货",想要避免吗?然后,也许您可以使用Mockery库而不必编写类。