堆栈版本:
Api平台3.2
Symfony 6.4
PhpUnit 9.5
PHP 8.0
为用户资源的 DELETE 端点编写测试。
调用端点时,必须软删除用户,并且必须将多个包含敏感数据的字段设置为空或用乱码覆盖。用户必须符合一些条件才可以删除。
通过查看返回的响应数据或从数据库检索用户,可以轻松检查端点是否执行预期操作。
我还被要求检查方法
NewsletterService::removeUserFromAlLists()
是否被调用。因此,我创建了一个模拟对象,并用模拟替换了测试容器中的 NewsletterService。
但是,在执行请求时,DeleteUserAction 会注入真正的 NewsletterService。为什么?
失败信息:
There was 1 failure:
1) App\Tests\Functional\Entity\UserTest::testErase()
Expectation failed for method name is "removeUserFromAlLists" when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
测试代码:
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Enum\User\UserErasedReasonsEnum;
use App\Entity\User;
use App\Factory\UserFactory;
use App\Service\NewsletterService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use Zenstruck\Browser\HttpOptions;
use Zenstruck\Browser\Test\HasBrowser;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
class UserTest extends ApiTestCase
{
use ResetDatabase;
use Factories;
use HasBrowser;
public function testErase(): void
{
static::bootKernel();
$container = static::getContainer();
$userToAuthenticate = UserFactory::createOne();
$user = UserFactory::createOne([]);
$newsletterServiceMock = $this->createMock(NewsletterService::class);
$newsletterServiceMock->expects($this->once())
->method('removeUserFromAlLists')
->with('email', $user->getEmail());
$container->set(NewsletterService::class, $newsletterServiceMock);
$erasedDate = (new \DateTime())->setTime(0, 0);
$erasedReason = (string)UserErasedReasonsEnum::WHATEVER;
$this->browser()
->disableReboot()
->actingAs($userToAuthenticate->_real())
->delete($this->getIriFromResource($user))
->assertSuccessful()
;
}
}
删除用户操作代码:
use App\Service\NewsletterService;
use Symfony\Bundle\SecurityBundle\Security;
use PHPUnit\Framework\MockObject\MockObject;
class DeleteAction
{
public function __construct(
private readonly Security $security,
private readonly NewsletterService $newsletterService,
) {}
public function __invoke(Request $request, int $id): Response
{
$user = $this->security->getUser();
$this->newsletterService->removeUserFromAlLists($user->getEmail());
// dd($this->newsletterService instanceof MockObject ? 'true' : 'false');
// When not commented returns false
// ...
}
}
问题是您在错误的容器中设置了服务的模拟。 ApiTestCase类创建的容器和Zenstruck Browser创建的容器不一样。您可以通过以下方法之一解决问题:
1。在 Zenstruck 浏览器容器中设置服务的模拟:
$this->browser()
->use(function () use ($newsletterServiceMock) {
self::getContainer()->set(NewsletterService::class, $newsletterServiceMock);
});
完整测试代码:
public function testErase(): void
{
static::bootKernel();
$container = static::getContainer();
$userToAuthenticate = UserFactory::createOne();
$user = UserFactory::createOne([]);
$newsletterServiceMock = $this->createMock(NewsletterService::class);
$newsletterServiceMock->expects($this->once())
->method('removeUserFromAlLists')
->with($user->getEmail());
$this->browser()
->disableReboot()
->use(function () use ($newsletterServiceMock) {
self::getContainer()->set(NewsletterService::class, $newsletterServiceMock);
})
->actingAs($userToAuthenticate->_real())
->delete($this->getIriFromResource($user))
->assertSuccessful();
}
2。您可以使用 Symfony 提供的应用程序测试功能。在这种情况下,您可以直接在
ApiTestCase
的容器中设置服务的模拟。
public function testErase(): void
{
$client = static::createClient();
$container = static::getContainer();
$userToAuthenticate = UserFactory::createOne();
$user = UserFactory::createOne([]);
$newsletterServiceMock = $this->createMock(NewsletterService::class);
$newsletterServiceMock->expects($this->once())
->method('removeUserFromAlLists')
->with($user->getEmail());
$container->set(NewsletterService::class, $newsletterServiceMock);
$response = $client->request('DELETE', '/api/users/' . $user->getId());
$this->assertResponseIsSuccessful();
}
注意: 您在创建模拟时也遇到错误。您仅将用户的电子邮件作为单个参数传递给
removeUserFromAlLists
函数。所以你只需要像这样设置期望:with($user->getEmail())
$newsletterServiceMock = $this->createMock(NewsletterService::class);
$newsletterServiceMock->expects($this->once())
->method('removeUserFromAlLists')
->with($user->getEmail());