Chapters
39 Chapters
|
4:45:13
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3, <8.0
Subscribe to download the code!Compatible PHP versions: ^7.1.3, <8.0
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
15.
Base Test Class full of Goodies
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
This tutorial works great for Symfony 5 and API Platform 2.5/2.6.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.1.3, <8.0",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.1", // v2.4.5
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^1.6", // 1.11.2
"doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
"doctrine/orm": "^2.4.5", // v2.7.2
"nelmio/cors-bundle": "^1.5", // 1.5.6
"nesbot/carbon": "^2.17", // 2.21.3
"phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
"symfony/asset": "4.3.*", // v4.3.2
"symfony/console": "4.3.*", // v4.3.2
"symfony/dotenv": "4.3.*", // v4.3.2
"symfony/expression-language": "4.3.*", // v4.3.2
"symfony/flex": "^1.1", // v1.21.6
"symfony/framework-bundle": "4.3.*", // v4.3.2
"symfony/http-client": "4.3.*", // v4.3.3
"symfony/monolog-bundle": "^3.4", // v3.4.0
"symfony/security-bundle": "4.3.*", // v4.3.2
"symfony/twig-bundle": "4.3.*", // v4.3.2
"symfony/validator": "4.3.*", // v4.3.2
"symfony/webpack-encore-bundle": "^1.6", // v1.6.2
"symfony/yaml": "4.3.*" // v4.3.2
},
"require-dev": {
"hautelook/alice-bundle": "^2.5", // 2.7.3
"symfony/browser-kit": "4.3.*", // v4.3.3
"symfony/css-selector": "4.3.*", // v4.3.3
"symfony/maker-bundle": "^1.11", // v1.12.0
"symfony/phpunit-bridge": "^4.3", // v4.3.3
"symfony/stopwatch": "4.3.*", // v4.3.2
"symfony/web-profiler-bundle": "4.3.*" // v4.3.2
}
}
33 Comments
Hey, everyone!
I am using Symfony 5.3 and I bumped into a situation that I would like to know your opinion to understand if this is the right way to deal with it.
In this version, the service <b>security.password_encoder</b> is deprecated in favour of the <b>security.password_hasher</b>, so when I run the tests I got the following error:
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "security.password_hasher" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.Since I think I cannot use dependency injection in this context (unit tests) I tried the other suggested solution: set it to public.
I ended up creating an alias in the <b>services_test.yaml</b> like this:
`
services:
`
And then use that alias, like:
`
$hashedPassword = self::getContainer()->get('test.security.password_hasher')
`
Is this the right way to deal with this situation?
Thank you in advance!
Hi, I have same error depreciation with Symfony 5.3 and I do the same thing as you did. I created file services_test.yaml in config and put any thing you write. But when I run the test, it says:
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "test.security.password_hasher".
Its seems the services_test.yaml not works.
Did I miss the step to tell phpunit use that config in services_test.yaml? Any other suggests to do the encode password stuff in test?
Many thanks
HeyDang
That's odd. By default, PHPUnit should run in the test environment so your Kernel will load both config files
services.yaml & services_test.yaml. Double check that the file lives insideconfig/and the name is correct. You could also try deleting the cache manuallyrm -r var/cacheCheers!
deleting the cache this way (instead of c:c) was key to solve the mistery here. thanks MolloKhan
Just in case anyone is wondering: you need to use
--env=testfor cache:clear to have an effect on your test cases. With this, the solution worked fine on my machine ;)services_test.yamlHey Jonas,
You're totally right! By default, the
cache:clearcommand will clear the cache for thedevenv, but if you want to clear it for a different env - you should specify it explicitly with--env=.... Thanks for pointing it out and confirming it works for you!Cheers!
Hey André,
I think in this situation it's fine what you did, and since you defined the service only for the test environment I don't see any potential problem to your approach.
Cheers!
Hey, Diego.
Nice! Thank you for the quick reply.
Keep up the great work and support!
Cheers!
Hi André,
Try something like this
`
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
class SomeClasse extends ApiTestCase
public function someFunction()
{
$factory = new PasswordHasherFactory(['auto' => ['algorithm' => 'bcrypt'], 'memory-hard' => ['algorithm' => 'sodium'],]);
$passwordHasher = $factory->getPasswordHasher('auto');
$hashedPassword = $passwordHasher->hash($plainPasssord);
$user = new User();
$user->setEmail($email);
$user->setUsername(substr($email, 0, strpos($email, '@')));
$user->setPassword($hashedPassword);
}
}
`
I solved it so.
--
Regards,
Alexander Sumkin
Thanks!! it worked for me in symfony 5.4
Hey @Alexander
Thanks for your work around, that's also valid but personally, I prefer the other option because you don't have to worry about instatiation the PasswordHasher service. Making services public for the test environment it's not a bad thing actually
Cheers!
Hi. I'm experiencing a strange thing with
ReloadDatabaseTrait;This Fails, unless i comment the use / reloaddbtrait ... weird no ?
Hey Cerpo,
Difficult to say, coudl you double check the namespace you've used in that class? Is it the same namespace we used in the screencast? Btw, did. you download the course code and start from the start/ directory? Or did you follow this course on your own project / newer Symfony version? Also, could you share the exact error message you see? It would help us to give you some hints on it :)
Cheers!
Hi Victor. Many thanks for the anwser.
Watching the next chapter, i had an intuition. I moved the self::createClient at 1st line of the function ... and guess what : Pb solved. I can now keep the ReloadTrait up and the test pass as it should.
Before moving the self::createClient on 1st line of the function, it was acting like the create user persist did not work (login failed, invalid credentials) . But I knew the persist was working, because when i commented the trait , all was fine ( user created, login passed ) .
I now think that the CreatClient triggered the Reload trait after the createUser. ( thus , why it's advise in the next chapter to put it on 1st line to let magic happen :) ) .
Hey Cerpo,
Great, happy to hear you were able to solve this problem, good thinking about watching the next chapter :)
Cheers!
I have to call `disableReboot()` on the client. Otherwise I get this error on my second request (the first request is the login request): "Cannot set session ID after the session has started." I use Symfony 4.4 and API Platform 2.6.8. Any idea why this is necessary for me an not for you?
Hey Roland W.!
Hmm. That indeed looks weird. It "feels" to me as if PHP is starting a real session... but it should be using a "mock" session in the test environment. I would check into that. Specifically, you should have some config - probably in
config/packages/test/framework.yamlthat looks like this: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.4/config/packages/test/framework.yaml - I would check there first. But, I'm doing some guessing. You have, unfortunately, a pretty old Symfony version. So we can't rule out that there is some weird behavior due to that :/.Cheers!
Hello everyone,
I am having an issue : My LogRessourceTest (aka CheeseListing) can't find my CustomApiTestCase.
docker-compose exec php bin/phpunit
PHP Fatal error: Uncaught Error: Class 'App\Test\CustomApiTestCase' not found in /srv/api/tests/Functional/LogResourceTest.php:8
Stack trace:
1) Why is that ?
2) Why do we move our CustomApiTestCase in src/Test ? That's make two tests folder, which can be confusing.
-> My test works if CustomApiTestCase is in Tests folder
PS : I'm on API Platform 2.5.9
Hey Ben G.!
Let's see what we can figure out :).
>1) Why is that ?
I'm not completely sure. As long as your CustomApiTestCase lives in a src/Test/CustomApiTestCase.php file and that file has the correct class name (CustomApiTestCase) and namespace (App\Test), Composer's autoloader should find it. These class not found things are usually something minor like that. The other possibility is that PHPUnit isn't loading your autoloader - that's normally handled thanks to your phpunit.xml.dist file - it has a bootstrap config that points to a file that initializes the autoloader - for example - https://github.com/symfony/recipes/blob/e6fdf8eff6dd5f5a19bc7aedfac491c60f2121cc/symfony/phpunit-bridge/5.1/phpunit.xml.dist#L8
That's a good question. This is a matter of "taste" My perspective is that CustomApiTestCase is not itself a "test" - it's a helper class. So I put it in the src/ directory. But that doesn't mean I'm correct. You could argue that even though this isn't itself a test class, it exists for our tests, not our normal code, and so should live in tests/.
And, you ARE free to put things in either src/ or test/. A Symfony project, by default, it set up to autoload from both folders. The default "new project" composer.json skeleton - https://github.com/symfony/skeleton/blob/5.x/composer.json - has this:
This means that you can put things into the tests/ directory, as long as the namespace starts with App\Tests.
So hopefully that explains how classes are loaded in both directories and the fact that you should totally feel ok putting tour CustomApiTestCase inside tests/ ;).
Cheers!
Thanks for your reactivity and you well explained anwser !
1) Ok, that's what I thought. When you start a new language/framework, is usually useful to ask dumb questions. I'll discuss what my team prefers.
2) Last time I did, I had no errors in PHPStorm. So I try again, and this time it works. I may have write my test folder in lowercase. Anyway, now it works and I totally got it, thanks again !
In my case the problem was that I created folder name in lowercase
"test" and had same error. after renaming to Test this works fine!
The test directory name has to match to what you have in the
autoload-dev.psr-4key and in yourphpunit.xmlfileCheers!
1) App\Tests\Functional\CheeseListingResourceTest::testCreateCheeseListing
TypeError: Argument 1 passed to App\ApiPlatform\Test\ApiTestCase::getHttpClient() must be an instance of App\ApiPlatform\Test\Client or null, instance of ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client given, called in /var/www/html/api_platform/src/ApiPlatform/Test/ApiTestCase.php on line 66
/var/www/html/api_platform/src/ApiPlatform/Test/ApiTestAssertionsTrait.php:126
/var/www/html/api_platform/src/ApiPlatform/Test/ApiTestCase.php:66
/var/www/html/api_platform/tests/Functions/CheeseListingResourceTest.php:15
I am getting above error while creating the test case for user.
Hey Vishal T.!
Let's see if we can figure this out :).
First, are you using API Platform 2.5? If so, then you don't need to copy any of our test code from this chapter - https://symfonycasts.com/sc... - because API Platform 2.5 has all of these classes. I believe you *are* using API Platform 2.5, which is why you're having the issue. So, you can make your life simpler by not copying any of those files and extending the ApiTestCase directly from ApiPlatform :).
But, to your specific error, I think you did copy the test files, but I think you might be missing this services_test.yaml file - https://symfonycasts.com/sc... (but I could be wrong, that's just a guess). However, either way, if you're on API Platform 2.5, the best solution is to just use *their* test code and delete your src/ApiPlatform/Test/ directory entirely).
Cheers!
Maybe it is beyond the scope of this example, but I think it is not the best practice to place all tests in one test method. For my part (I am not following exactly), I created 3 methods:
testCreateUser, the normal working path,testCreateUserNotLoggedIn, to ensure a 401 if there is no logged in user, and finallytestCreateUserInsufficientRolesfor a logged in user with insufficient roles.Now, if one test fails, I can see immediately which part fails and I can focus exactly on the broke path. Also, the tests don't depend on each other.
Hey Sebastian,
Yes, I think I'd agree with you. Well, it depends... first of all, it's not a course about testing, so we didn't want to focus on testing strategy too much, mostly just the general idea of it, and for simplicity doing those things in one method was great as you can see, a little code, and users who does not care about testing (though we hope we don't have such users here :p ) may easily skip that part. Also, in defense of the current implementation I'd say that you can also easily add custom errors messages to PHPUnit's assert methods that would also give you a lot of context about what exactly test failed. But you're right, best practice would be to split those tests. And I think we're talking about this in our PHPUnit course here: https://symfonycasts.com/sc... - which has focus on testing.
Thank you for understanding!
Cheers!
Yep, I'd agree too :). I admit, I was taking some shortcuts on this one. A best practice would be to do exactly what you said. I will *also* admit that I'm not perfect on this - it's tempting to "re-use" your already-setup-and-ready-test to assert more things". But yea, ultimately, you "pay the price" later on when it's harder to debug :).
Cheers!
Hi Ryan,
Thank you very much for your dynamism ;)
I have one question : Why did you create the CustomApiTestCase into the src/ directory. After all, it's a class that'll be used only for tests. From an organization point of view, don't you think that is better if we put it directly under the tests/ directory ?
I also have another note: I think that the input of the logIn function must be the email and the password only. The $client object that you passed, I think it should be something that the class provides. For me, I create something like this
`
namespace App\Tests;
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\User;
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client;
class CustomApiTestCase extends ApiTestCase
{
`
Looking forward to hearing your feedback :)
Cheers :)
Hey Amjed N.
Good questions. I suppose holding
CustomApiTestCaseinsidesrc/is just to separate some reusable code from tests, and to keeptests/directory clean only for executable tests.And about your note sometimes it's better to not reuse existing
$clientobject, and instantiate new one for each test. Because client can save some info about previous request and this can cause unexpected behavior.Cheers :)
Hi sadikoff
I understand your point about having a clean
tests/directory but the thing is, my production build is loading myCustomApiTestCaseand trying to load Alice'sReloadDatabaseTraitas well.Of course, alice is dev dependency so it doesn't work.
I'm fairly new to php and autoloaders, do you have any tips to avoid loading the Test directory outside of testing env?
I could probably do as amjed said and move the
Testdirectory intests/but then I can't get my imports to work.I tried having a tests/Test directory with the namespace
App\Test, thenApp\Test\Test. Nothing :(Looking forward to your answer !
Hey Philippe B.
Try to exclude
Test/directory from autowiring. By default symfony excludesTests/directory you can find it insideconfig/services.yamlfile look atApp\:definition. Just add yourTestdirectory or rename your directory to correspond default configuration.Cheers!
Hi sadikoff
Perfect, it works like a charm!
Thank you so much for your quick reply
Cheers!
Hey Vladimir,
Thank you for your respond.
Okay for the first point.
For the second one, you're right we must instanciate for each test a client. So I put the instansation of the $client in the setUp() method into the CustomApiTestCase.
Cheers :)
"Houston: no signs of life"
Start the conversation!