我有一个使用Symfony2编写的API,我正在尝试编写post hoc测试。其中一个端点使用电子邮件服务向用户发送密码重置电子邮件。我想嘲笑这项服务,以便检查是否有正确的信息发送到服务,并防止实际发送电子邮件。
这是我试图测试的路线:
/**
* @Route("/me/password/resets")
* @Method({"POST"})
*/
public function requestResetAction(Request $request)
{
$userRepository = $this->get('app.repository.user_repository');
$userPasswordResetRepository = $this->get('app.repository.user_password_reset_repository');
$emailService = $this->get('app.service.email_service');
$authenticationLimitsService = $this->get('app.service.authentication_limits_service');
$now = new \DateTime();
$requestParams = $this->getRequestParams($request);
if (empty($requestParams->username)) {
throw new BadRequestHttpException("username parameter is missing");
}
$user = $userRepository->findOneByUsername($requestParams->username);
if ($user) {
if ($authenticationLimitsService->isUserBanned($user, $now)) {
throw new BadRequestHttpException("User temporarily banned because of repeated authentication failures");
}
$userPasswordResetRepository->deleteAllForUser($user);
$reset = $userPasswordResetRepository->createForUser($user);
$userPasswordResetRepository->saveUserPasswordReset($reset);
$authenticationLimitsService->logUserAction($user, UserAuthenticationLog::ACTION_PASSWORD_RESET, $now);
$emailService->sendPasswordResetEmail($user, $reset);
}
// We return 201 Created for every request so that we don't accidently
// leak the existence of usernames
return $this->jsonResponse("Created", $code=201);
}
然后我有一个ApiTestCase
类,它扩展了Symfony WebTestCase
以提供辅助方法。此类包含尝试模拟电子邮件服务的setup
方法:
class ApiTestCase extends WebTestCase {
public function setup() {
$this->client = static::createClient(array(
'environment' => 'test'
));
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$this->mockEmailService = $mockEmailService;
}
然后在我的实际测试案例中,我试图做这样的事情:
class CreatePasswordResetTest extends ApiTestCase {
public function testSendsEmail() {
$this->mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
}
所以现在的诀窍是让控制器使用模拟版本的电子邮件服务。我已经阅读了几种不同的方法来实现这一点,到目前为止我没有太多运气。
方法1:使用container-> set()
请参阅How to mock Symfony 2 service in a functional test?
在setup()
方法中告诉容器在被要求提供电子邮件服务时应该返回的内容:
static::$kernel->getContainer()->set('app.service.email_service', $this->mockEmailService);
# or
$this->client->getContainer()->set('app.service.email_service', $this->mockEmailService);
这根本不会影响控制器。它仍然称为原始服务。我看到一些写作提到被模拟的服务在一次调用后被“重置”。我甚至没有看到我的第一个电话被嘲笑,所以我不确定这个问题是否影响了我。
我应该打电话给set
吗?
或者我太嘲笑这项服务了?
方法2:AppTestKernel
请参阅:http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html 请参阅:Symfony2 phpunit functional test custom user authentication fails after redirect (session related)
当谈到PHP和Symfony2时,这个让我脱离了深度(我不是真正的PHP开发者)。
目标似乎是改变网站的某种基础类,以允许我的模拟服务在请求的早期注入。
我有一个新的AppTestKernel
:
<?php
// app/AppTestKernel.php
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
$this->kernelModifier = null;
};
}
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
// We force the kernel to shutdown to be sure the next request will boot it
$this->shutdown();
}
}
我ApiTestCase
中的新方法:
// https://stackoverflow.com/a/19705215
protected static function getKernelClass(){
$dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
$finder = new Finder();
$finder->name('*TestKernel.php')->depth(0)->in($dir);
$results = iterator_to_array($finder);
if (!count($results)) {
throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
}
$file = current($results);
$class = $file->getBasename('.php');
require_once $file;
return $class;
}
然后我改变我的setup()
以使用内核修饰符:
public function setup() {
...
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
static::$kernel->setKernelModifier(function($kernel) use ($mockEmailService) {
$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
});
$this->mockEmailService = $mockEmailService;
}
这个有效!但是,当我尝试做这样的事情时,我现在无法在其他测试中访问容器:
$c = $this->client->getKernel()->getContainer();
$repo = $c->get('app.repository.user_password_reset_repository');
$resets = $repo->findByUser($user);
getContainer()
方法返回null。
我应该以不同方式使用容器吗?
我是否需要将容器注入新内核?它扩展了原始内核,因此我不知道为什么/它在容器内容方面有什么不同。
方法3:替换config_test.yml中的服务
请参阅:Symfony/PHPUnit mock services
此方法要求我编写一个覆盖电子邮件服务的新服务类。编写像这样的固定模拟类似乎没有常规动态模拟那么有用。如何测试某些参数调用某些方法?
方法4:在测试中设置所有内容
继续@Matteo的建议我写了一个测试来做到这一点:
public function testSendsEmail() {
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
static::$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
$this->client->getContainer()->set('app.service.email_service', $mockEmailService);
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
此测试失败,因为未调用预期的方法sendPasswordResetEmail
:
There was 1 failure:
1) Tests\Integration\Api\MePassword\CreatePasswordResetTest::testSendsEmail
Expectation failed for method name is equal to <string:sendPasswordResetEmail> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
答案 0 :(得分:0)
感谢Cered的建议,我已经成功地开始工作,可以测试我希望实际发送的电子邮件。我无法让嘲笑工作,所以我有点不愿意将此标记为“答案”。
这是一项检查发送电子邮件的测试:
public function testSendsEmail() {
$this->client->enableProfiler();
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
$mailCollector = $this->client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$this->assertInstanceOf('Swift_Message', $message);
$this->assertEquals('Reset your password', $message->getSubject());
$this->assertEquals('info@example.com', key($message->getFrom()));
$this->assertEquals($this->user->getEmail(), key($message->getTo()));
$this->assertContains(
'This link is valid for 24 hours only.',
$message->getBody()
);
$resets = $this->getResets($this->user);
$this->assertContains(
$resets[0]->getToken(),
$message->getBody()
);
}
它的工作原理是启用Symfony探查器并检查swiftmailer服务。它在此处记录:http://symfony.com/doc/current/email/testing.html