I'm writing functional tests for my symfony3 application and I have a problem that when I'm trying to test sending forms, by crawling the submit button.
Method what I want to test:
$personalDataForm->handleRequest($request);
if ($personalDataForm->isValid()) {
return $this->handlePersonalForm($personalDataForm, $request);
}
return $this->render('members/personal-form.html.twig', [
'personalDataForm' => $personalDataForm->createView(),
]);
Functional test:
$client = static::createClient();
$client->getCookieJar()->set($this->cookie);
$client->followRedirects();
$container = $client->getContainer();
$container->set('session', $this->session);
$crawler = $client->request('GET', '/');
$form = $crawler->selectButton('Save')->form();
$client->submit($form);
Declaration of $this->session:
$this->session = new Session(new MockArraySessionStorage(), new AttributeBag(), new FlashBag())
The crawler is correctly sending a request, but I have an error Failed to start the session because headers have already been sent by "phar:///usr/local/bin/phpunit/phpunit/Util/Printer.php" at line 134.
When I'm looking at test result I see that my test didn't pass the $personalDataForm->isValid() condition.
I think the problem is that after submitting form, application is using Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage to store a session instead of mocked session. How can I pass mocked session when I'm submitting form?
Related
I am implementing functional tests for my REST-Api. The Api is protected by authorization. For this I chose the json_login provider. So far, so good. Authentication works when accessing in the normal environment via Insomnia.
Now I want functional tests. For that, I create an user via the configured User-class and persist it in the database. Works as expected.
But of course the test only works once as the user already exists in the following tests.
So I tried hautelook/alice-bundle with ResetDatabaseTrait or ReloadDatabaseTrait as well as dmaicher/doctrine-test-bundle.
Both show the same behaviour: The authenticator can not find the newly created user. (EntityUserProvider::loadUserByUsername finds no user)
Apparently the EntityUserProvider seems to use a different "connection" into the database that can not look into the transaction those libraries started.
The entity-manager in my test that is responsible for persisting my user is created either with
protected function setUp(): void {
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
or directly before creating the user with
$em = self::$container->get('doctrine')->getManager();
which seems correct for me. But in any case I get the same result -> "Invalid credentials" because the user can not be found.
Maybe someone out there can point me into the right direction?
After a refreshing break I remembered a detail when I was creating my tests. All the examples did not need a setUp-Method with self:bootKernel() in it. But without it the self::$container property was empty, so I added that to my test-class. Maybe there was the solution to the problem?
I was right!
I am using the Api-Platform package. Therefore my test-class is based in ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase. That class does not have a setUp Method, but inspecting createClient() I noticed that there the kernel is created by calling bootKernel() which also stops any running kernel.
So my setUp() method created a kernel. With that kernel I created my user.
Then I called createClient() to create the test-client for the requests. This killed my initial kernel and creates a new one which then leads to the problems.
Rearranging the statements - first create the client, then get the EntityManager from the now created container and create the User after creating the client solved the problem.
After two days , hooh
when you want to call multiple request, for example if you want at first request you get token and the second you call with this token and check auth, in during this calls if you use use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait trait your data base rest after first call, you have token but database is empty, and second call fail.
So, read again this important part of documentation :
There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests. Writing Functional Tests
I, know we are lazy developer and first read code, not document :-)
$client = static::createClient();
$client->disableReboot();
$manager = self::getContainer()->get('doctrine')->getManager();
$user = new User();
$user->setEmail('user#example.com');
$user->setPassword(
self::getContainer()->get('security.user_password_hasher')->hashPassword($user, $password = 'pass1234')
);
$manager->persist($user);
$manager->flush();
$response = $client->request('POST', '/authentication-token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'email' => $user->getEmail(),
'password' => $password ,
],
]);
$token = $response->toArray()['token'] ?? null;
$client->request('GET', '/profile', [
'auth_bearer' => $token
]);
self::assertResponseIsSuccessful();
One of my services depends on the HTTP_HOST value in the currentRequest object from the requestStack. When this service is used in a functional test it works because I create the client with the host parameter:
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
At some point I have the need to get a service from the container that has a dependency on the request so i thought i used the client created with the host value to fetch the service:
$client->getKernel()->getContainer()->get('service')->someMethod();
But the request object is no longer set when the constructer of this service is is called.
Is there any way I can use this service in the test function with a dependency on the Request object ?
Related code:
ControllerTest.php
//Create client with HTTP_HOST
$client = static::createClient(array(), array(
'HTTP_HOST' => 'test.' . $this->domain
));
//Do some request services depending on the request object work because the client is initiated with the HTTP_HOST value
$crawler = $client->request('GET', $redirectUrl);
$this->assertEquals(
1,
$crawler->filter('html:contains("feedback")')->count()
);
//Now I want to check if email feedback is send. This process starts in a EventSubsriber
//I have to trigger this event myself because the $event variable consist of fake data.
$client->getContainer()->get('event_subscriber')->process($event);
//now collect the mail and do some checks
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
You should get the Container directly from your created client, as described in the official docs:
$client->getContainer()->get('service')->someMethod();
It may still be necessary to mock the whole service but more code examples would be needed..
I created UnitTests for my Symfony app with the REST and OAuthBundle. To test the API behind the firewall, I create in my setUp method a UsernamePasswordToken by
$token = new UsernamePasswordToken($user, null, 'default', array('ROLE_USER'));
Now I set the token by
self::$client->getContainer()->get('security.token_storage')->setToken($token);
Interestingly this token is only for one request in the storage. The first request with the first assertion succeeds, the second fails because of an 401 error. I checked the storage afterwards and the getToken() method returns NULL. If I set the token once more before the next request, this request succeeds also.
This is a sample request and the assertion:
$crawler = self::$client->request('GET', '/api/users');
$this->assertEquals(200, self::$client->getResponse()->getStatusCode());
So, I can set the token before each single request to solve the problem, but this would very annoying in all my tests. Why is the token after one "use" gone and how can I set a "lifetime" or something else?
I think the problem is that each request the kernel and with it the container will load from the cache again where it does not contain your token. You have to persist your token in the session for it to stay permanently. How to do this is described in the documentation Testing HTTP Authentication
protected function login()
{
$session = $this->client->getContainer()->get('session');
// the firewall context defaults to the firewall name
$firewallContext = 'secured_area';
$token = new UsernamePasswordToken('admin', null, $firewallContext, array('ROLE_ADMIN'));
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
This should work over multiple requests and you can set it per test-method if you still want some tests not to be logged in.
I try to authorize a test user in my UnitTest cases. For this I create for the tests the following helper function (that is not very handy, but I can make this better afterwards):
public function generateOAuthLoginData(EntityManager $em, Client $client) {
$apiclient = new \OAuthBundle\Entity\Client();
$apiclient->setRandomId('randomid');
$apiclient->setSecret('secret');
$apiclient->setAllowedGrantTypes(['password', 'refresh_token']);
$em->persist($apiclient);
$user = new \AppBundle\Entity\User();
$user->setEmail('user#test.de');
$user->setUsername('user');
$user->setPlainPassword('password');
$user->setFirstname('User');
$user->setLastname('Test');
$user->addRole('ROLE_ADMIN');
$em->persist($user);
$em->flush();
$crawler = $client->request('GET', '/oauth/v2/token?client_id=1_randomid&client_secret=secret&grant_type=password&username=user#test.de&password=password');
$access_token = json_decode($client->getResponse()->getContent())->access_token;
return ['ACCEPT' => 'application/json', 'AUTHORIZATION' => 'Bearer '.$access_token];
}
I get a access token (checked it in the code and in the database) and now I try to make some API requests by executing
$crawler = self::$client->request('GET', '/api/users', [], [], self::$authorisationHeaders);
$this->assertEquals(200, self::$client->getResponse()->getStatusCode());
in the test class.
I tried with RESTer (a Firefox plugin to make custom requests) and there it works. But in the tests I get a 401 error. Here is the error out of the log file:
[2017-04-18 13:45:32] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /var/www/testproject/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/AccessListener.php:53)"} []
[2017-04-18 13:45:32] security.DEBUG: Calling Authentication entry point. [] []
What's my fault? And why does it work in RESTer and not in the UnitTests?
Did you tried to use HTTP_Authorization instead of AUTHORIZATION as an array key?
How do I send additional parameters with a request in a functional test in Symfony2. I have
$client = static::createClient();
$crawler = $client->request("GET", '/timezones/23.html?X=1', array("rest_auth" => "wrong"));
Both X and rest_auth are missing when the request hits my Symfony2 application. I have tried it with POST too and even with
json_encode(array("rest_auth" => "wrong"))
Nothing seems to work in sending additional query parameters to the request.
It turns out that this will work for the passed params
$req->get("timezone")
But this will not
$_REQUEST['timezone']
$_GET['timezone']