8

As the title says, I'm building an API with Symfony 5. I have some controllers that require different user permissions that I'd like to test, so I decided to create two users with different roles for testing purpose - ROLE_USER and ROLE_ADMIN. The current code is like this (note, it's not full code, just a dummy example/starting point)

ApiTestCase.php

<?php

namespace App\Tests;

use App\Entity\User;
use App\Tests\Http\RequestBuilder;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ApiTestCase extends WebTestCase
{

    private static $userData = [
        'firstName' => 'Pera',
        'lastName'  => 'Peric',
        'email'     => '[email protected]',
        'password'  => 'test123',
        'roles'     => [
            'ROLE_USER'
        ]
    ];

    private static $adminUserData = [
        'firstName' => 'Admin',
        'lastName'  => 'Adminovic',
        'email'     => '[email protected]',
        'password'  => 'admin123',
        'roles'     => [
            'ROLE_ADMIN'
        ]
    ];

    public static function createTestUser()
    {
        $res = RequestBuilder::create(self::createClient())
            ->setMethod('POST')
            ->setUri('/api/v1/security/register')
            ->setJsonContent(self::$userData)
            ->getResponse();

        $data = $res->getJsonContent();
        return $data['data'];
    }

    public static function createTestAdminUser()
    {
        $res = RequestBuilder::create(self::createClient())
            ->setMethod('POST')
            ->setUri('/api/v1/security/register')
            ->setJsonContent(self::$adminUserData)
            ->getResponse();

        $data = $res->getJsonContent();
        var_dump($data);
        return $data['data'];
    }

    public static function deleteTestUser()
    {
        self::createClient()->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(User::class)
            ->deleteUserByEmail(self::$userData['email']);
    }

    public static function deleteTestAdminUser()
    {
        self::createClient()->getContainer()
            ->get('doctrine.orm.entity_manager')
            ->getRepository(User::class)
            ->deleteUserByEmail(self::$adminUserData['email']);
    }

}

LocationControllerTest.php


namespace App\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class LocationControllerTest extends ApiTestCase
{
    public static $user;
    public static $adminUser;

    public static function setUpBeforeClass()
    {
        self::$user = self::createTestUser();
        self::$adminUser = self::createTestAdminUser();
    }

    public function testUsersExist()
    {
        // this is jut to test out an idea
        $this->assertContains('ROLE_USER', self::$user['roles']);
        $this->assertContains('ROLE_ADMIN', self::$adminUser['roles']);
    }

    public static function tearDownAfterClass()
    {
        self::deleteTestUser();
        self::deleteTestAdminUser();
    }
}

When I run the test (sunit is an alias php bin/phpunit):

➜  skeleton master ✗ (*?) sunit --filter LocationController                                                                                                              [10:12AM]
PHPUnit 7.5.17 by Sebastian Bergmann and contributors.

Testing Project Test Suite
E                                                                   1 / 1 (100%)

Time: 742 ms, Memory: 24.00 MB

There was 1 error:

1) App\Tests\LocationControllerTest::testUsersExist
LogicException: Booting the kernel before calling Symfony\Bundle\FrameworkBundle\Test\WebTestCase::createClient() is not supported, the kernel should only be booted once. in /Users/shkabo/code/skeleton/vendor/symfony/framework-bundle/Test/WebTestCase.php:44
Stack trace:
#0 /Users/shkabo/code/skeleton/tests/ApiTestCase.php(45): Symfony\Bundle\FrameworkBundle\Test\WebTestCase::createClient()
#1 /Users/shkabo/code/skeleton/tests/LocationControllerTest.php(16): App\Tests\ApiTestCase::createTestAdminUser()
#2 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/src/Framework/TestSuite.php(703): App\Tests\LocationControllerTest::setUpBeforeClass()
#3 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/src/Framework/TestSuite.php(746): PHPUnit\Framework\TestSuite->run(Object(PHPUnit\Framework\TestResult))
#4 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/src/TextUI/TestRunner.php(652): PHPUnit\Framework\TestSuite->run(Object(PHPUnit\Framework\TestResult))
#5 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/src/TextUI/Command.php(206): PHPUnit\TextUI\TestRunner->doRun(Object(PHPUnit\Framework\TestSuite), Array, true)
#6 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/src/TextUI/Command.php(162): PHPUnit\TextUI\Command->run(Array, true)
#7 /Users/shkabo/code/skeleton/bin/.phpunit/phpunit-7.5-0/phpunit(17): PHPUnit\TextUI\Command::main()
#8 /Users/shkabo/code/skeleton/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php(291): include('/Users/shkabo/c...')
#9 /Users/shkabo/code/skeleton/bin/phpunit(13): require('/Users/shkabo/c...')
#10 {main}
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

I understand the error, but can't seem to find a solution for it/this approach. Also it's highly possible that I'm doing it the wrong way.

In the database, first user is created while 2nd one throws this error when I try to create it.

What I want to achieve is that I have (static) methods which I could call and create dummy user/location/etc. and use it in testing of that specific controller, and destroy it/delete it from db upon completing tests of that specific controller.

RequestBuilder https://github.com/nebkam/fluent-test/blob/master/src/RequestBuilder.php

3 Answers 3

24

You can call self::ensureKernelShutdown() before creating your client as per the official recommendation.

self::ensureKernelShutdown();
$sally = static::createClient();

I am not sure this is a fix for this very question, but I had a similar problem and Google landed me here.

https://github.com/symfony/symfony-docs/issues/12961

Sign up to request clarification or add additional context in comments.

2 Comments

This did the trick for me (even if $client->getKernel->shutdown() didn't worked... beats me). Thanks pal !
in Symfony4.4 this made my lucky. No luck with Symfony 5.2 though
5

The issue is because you are using 2 times static::createClient() and this will boot again the kernel. To avoid that you can create a client and then clone it and modify some parameters using a method and passing the client as refefence.

Here you can find a question I've wrote yesterday in the repo and the solution I found

private static ?KernelBrowser $client = null;
    protected static ?KernelBrowser $admin = null;
    protected static ?KernelBrowser $user = null;

    /**
     * @throws \Exception
     */
    public function setUp(): void
    {
        $this->resetDatabase();

        if (null === self::$client) {
            self::$client = static::createClient();
        }

        if (null === self::$admin) {
            self::$admin = clone self::$client;
            $this->createAuthenticatedClient(self::$admin, '[email protected]', 'password');
        }

        if (null === self::$user) {
            self::$user = clone self::$client;
            $this->createAuthenticatedClient(self::$user, '[email protected]', 'password');
        }
    }

    protected function createAuthenticatedClient(KernelBrowser &$client, string $username, string $password): void
    {
        $client->request(
            'POST',
            '/api/v1/login_check',
            [
                '_email' => $username,
                '_password' => $password,
            ]
        );

        $data = \json_decode($client->getResponse()->getContent(), true);

        $client->setServerParameter('HTTP_Authorization', \sprintf('Bearer %s', $data['token']));
        $client->setServerParameter('CONTENT_TYPE', 'application/json');
    }

More details at https://github.com/symfony/symfony/issues/35031

Comments

0

I had the same issue and solved by initializing the Client in the setUp()

protected function setUp(): void
{
    $this->client = $this->makeClient();
    $this->client->followRedirects();
    ...
}

and in the test I use $this->client->loginUser() to login the user giving the authentication firewall type as second parameter

public function testWhateverYouNeed(): void
{
    ... // Get the user from fixtures
    $this->client->loginUser($user, 'admin');
}

The firewalls are defined in config/packages/security.yaml

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.