在測試中將 Controller 的 EntityManager 替換成測試中 Mock 生成的實例
在 Controller 中我們常會直接使用內建的 EntityManager 來操作資料庫, 然而因為這個 EntityManager 並不是透過 Service Injection 傳入的, 所以我們沒有辦法直接透過外部傳入,本文章主要是用來紀錄解決方法。
前情提要
首先我們先來寫一個範例用的 Controller:
class FetchProductController extends AbstractController {
public function __invoke(Request $request, string $id) : Response
{
$entityManager = $this->getDoctrine()->getManager();
$productRepository = $entityManager->getRepository(Product::class);
$product = $productRepository->find($id);
// ... handle product here
return new Response();
}
}
這時候就會發現 $entityManager
來自 Controller 內部,但是如果我們想要 Mock ProductRepository 的話, 我們必須先 Mock EntityManager。
解法
這裡是一個測試的一小部份。
class FetchProductControllerTest extends KernelTestCase
{
public function testFetchProduct()
{
$kernelInstance = static::bootKernel();
$product = new Product();
$product->setName("Product Name");
// set product data
/**
* Mock ProductRepository
*/
$productRepository = $this->getMockBuilder(ProductRepository::class)
->disableOriginalConstructor()
->getMock();
$productRepository
->expects($this->any())
->method("find")
->will($this->returnValue($product));
/**
* Mock EntityManager
*/
$entityManager = $this->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$entityManager
->expects($this->any())
->method("getRepository")
->will($this->returnValueMap(array(
array(Product::class, $productRepository)
)));
/**
* Set default entity manager
*/
$kernelInstance
->getContainer()
->set("doctrine.orm.default_entity_manager", $entityManager);
// Test Controller
}
}
其中最重要的就是這行
$kernelInstance
->getContainer()
->set("doctrine.orm.default_entity_manager", $entityManager);
這是因為 Symfony 中的 Service 在沒有特別設定的狀態下,都會以 singleton 的方式存在, 所以不管在哪裡跟 kernel 要求 EntityManager 物件,都會拿到同一個, 因此在這裡直接將 Default entity manager 設定成我們自己 mock 出來的 EntityManager, 這樣就可以讓在 Controller 裡面的 $this->getDoctrine()->getManager()
的回傳值換成我們的 EntityManager。 `