phpsymfonyphpunitsymfony-voter

Symfony - testing vote TokenInterface not invoked


I'm trying to write a PHPUnit test in my Symfony project for a Voter class.

class CustomVoter extends Voter
{
private MyCustomRepository $myCustomRepository;

public function __construct(MyCustomRepository $myCustomRepository)
{
    $this->myCustomRepository = $myCustomRepository;
}

protected function supports(
    string $attribute,
           $subject
): bool
{
    if (!in_array($attribute, ['edit'])) {
        return false;
    }
    return true;
}

protected function voteOnAttribute(
    string $attribute,
    $subject,
    TokenInterface $token
): bool
{
    $user = $token->getUser();
    if (!$user instanceof UserInterface) {
        return false;
    }

    return $this->myCustomRepository->hasAccess(
        $user->getId(),
        $attribute,
        $subject
    );
  } 
}

I have a problem mocking TokenInterface.

In my test I am returning a user from my test database:

private $entityManager;

protected TokenInterface|MockObject|null $token = null;

public function setUp(): void
{
    $kernel = self::bootKernel();

    $this->entityManager = $kernel->getContainer()
        ->get('doctrine')
        ->getManager();

    $user = $this->entityManager
        ->getRepository(Member::class)
        ->findOneBy(['email' => 'user@test.com']);

   $this->token = $this->getMockBuilder(TokenInterface::class)
        ->disableOriginalConstructor()
        ->disableOriginalClone()
        ->disableArgumentCloning()
        ->disallowMockingUnknownTypes()
        ->getMock();

    $this->token->expects($this->once())
        ->method('getUser')
        ->willReturn($user);
}

And when trying to test the voter:

/**
 * @dataProvider provideCases
 */
public function testVote(array $attributes, string $subject, ?TokenInterface $token, $expectedVote) {
    $this->assertEquals($expectedVote, $this->voter->vote($token, $subject, $attributes));
}

public function provideCases(): \Generator
{

    yield 'user can edit' => [
        ['edit'],
        'my_subject',
        $this->token,
        VoterInterface::ACCESS_GRANTED,
    ];
}

And I get:

Expectation failed for method name is "getUser" when invoked 1 time(s). Method was expected to be called 1 times, actually called 0 times.

What is the case here? When dd($user); I get user object from the database..


Solution

  • I can't write a comment so I will post the answer. I have a feeling that we need to see your full Voter class code to be able to see what is going on.

    I think because of wrong attribute or something the vote() method finish the check earlier (before voteOnAttribute() call) so getUser() is not called in your case.

    I will try to edit or delete this answer if I figure out how to help you.

    EDIT

    I tested your case in my local environment and I can say that you should pass the token directly in test method, not via dataProvider.

        /**
         * @dataProvider provideCases
         */
        public function testVote(array $attributes, string $subject, $expectedVote) {
            $this->assertEquals($expectedVote, $this->voter->vote($this->token, $subject, $attributes));
        }
    

    Explanation:

    As we can read from the documentation:

    All data providers are executed before both the call to the setUpBeforeClass static method and the first call to the setUp method. Because of that you can't access any variables you create there from within a data provider.