Symfony 5 Api Testing createClient() LogicalException
Asked Answered
M

3

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

Modernity answered 18/12, 2019 at 9:59 Comment(0)
E
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

Edouard answered 19/12, 2019 at 9:25 Comment(0)
L
22

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

Livre answered 15/9, 2020 at 18:2 Comment(2)
This did the trick for me (even if $client->getKernel->shutdown() didn't worked... beats me). Thanks pal !Protuberate
in Symfony4.4 this made my lucky. No luck with Symfony 5.2 thoughSupra
E
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

Edouard answered 19/12, 2019 at 9:25 Comment(0)
M
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

Madura answered 24/8, 2020 at 13:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.