This tutorial is built using Symfony 4, but most of the concepts apply fine to Symfony 5!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"doctrine/annotations": "^1.8", // v1.8.0
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knpuniversity/lorem-ipsum-bundle": "*@dev", // dev-master
"nexylan/slack-bundle": "^2.0,<2.2", // v2.0.1
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.1.6
"symfony/asset": "^4.0", // v4.0.6
"symfony/console": "^4.0", // v4.0.6
"symfony/flex": "^1.0", // v1.21.6
"symfony/framework-bundle": "^4.0", // v4.0.6
"symfony/lts": "^4@dev", // dev-master
"symfony/twig-bundle": "^4.0", // v4.0.6
"symfony/web-server-bundle": "^4.0", // v4.0.6
"symfony/yaml": "^4.0", // v4.0.6
"weaverryan_test/lorem-ipsum-bundle": "^1.0" // v1.0.0
},
"require-dev": {
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"sensiolabs/security-checker": "^4.1", // v4.1.8
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.6
"symfony/dotenv": "^4.0", // v4.0.6
"symfony/maker-bundle": "^1.0", // v1.1.1
"symfony/monolog-bundle": "^3.0", // v3.2.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.3
"symfony/stopwatch": "^3.3|^4.0", // v4.0.6
"symfony/var-dumper": "^3.3|^4.0", // v4.0.6
"symfony/web-profiler-bundle": "^3.3|^4.0" // v4.0.6
}
}
21 Comments
Hey there,
Is it possible to pass custom configuration to a service when using the KernelTestCase with?
Cheers
Hey @julien_bonnier!
Yes, this is possible but... not super straight forward. The best method I've found is to use separate test-environments for different configuration permutations.
I do this in zenstruck/messenger-test:
TestKernel, load these different yaml files based on the environment nameself::bootKernel()Hope that helps!
Kevin
@kbond It feels a bit hackish, but it works so I ended up doing it.
Also, worth mentioning that you could remove the different config files and use
Ah, that is a great idea! So you only need to manage a single config file. I like this much better.
In messenger-test, I combine multiple configs using import - just to reduce duplication but... I think it's
worth the duplication to better understand the full environment.
Awwww....
Thank you @kbond for the answer.
I think that way is not great, but if this is the only way to do it... I guess, I'll have to.
Thanks again
Getting stuck in solving the following problem.
I’m building an application which accepts a command from the front-end and “broadcasts” it to all the services available. Simple as that. Services mentioned are supposed to be an access layer for third-party REST APIs.
I would like to implement each service as a separate bundle which in its turn must implement one of my “internal” contracts/interfaces. Hope it sounds reasonable as in this case I could just give my contracts to outsourcing developers not allowing them inside my main application.
I don’t want to restrict each bundle in its own dependencies. I just want them to implement my interface(s). But let’s assume that one of those bundles would like to use
symfony/http-clientas a dependency to talk to a third-party REST API. Symfony HttpClient Component is rather good (no need to use Guzzle, etc.) and moreover supportsscoped_clients. Got the idea? I have a dependency of Symfony HttpClient in my main application and would like that each bundle could configure it for yourself. Thus I would like to set up that scoped HttpClient straight in@MyBundle/Resources/config/services.xml. Does it violate any reasonable logic of the Symfony framework? Is this allowed? If so what I’m doing wrong if I get an error while putting something like this https://gist.github.com/erop/44dc45aee6f7c7c47b9d50bc7e5bf714 . The error message is ``In XmlFileLoader.php line 681:There is no extension able to load the configuration for "framework:config" (in /app/libs/<bundle_dir>/src/DependencyInjection/../Resources/config/services.xml). Looked for namespace "http://symfony.com/schema/dic/symfony", found none `` This is the first point I’m getting stuck.
Looks like I miss some concepts of service container. Or I’m doing something wrong to properly set it up. Could someone give a direction for solving the issue?
Looks like the answer for my question is here https://symfony.com/doc/cur... . In fact I implemented PrependeExtensionInterface for 'framework' extension and it works OK now!
Hey erop!
To give you a bigger answer about this, I'll mention 2 things :).
1) When you're inside your bundle's "extension" class (which is what you use to your bundles services.xml file), you're passed a
ContainerBuilderobject. But that is NOT the "main" ContainerBuilder. Symfony passes each bundle an empty ContainerBuilder. Then, you add your services to it and - eventually - Symfony merges all of them together. It does that so that each bundle can't "interfere" with the services for other bundles. THAT is why you got the "There is no extension able...." thing - only the "main" ContainerBuilder is aware of the ability to process the "framework" config. That's also on purpose - the bundle-config (like framework, or twig, or doctrine) is really meant to be used by end-users and not by applications.2) So, most of the time, when you want to configure another bundle in some way, there is some non-configuration hook point to do it. Maybe you need to create an event listener, or create a service with some special tag to "hook into" the system. What you're doing is, sort of, the "last resort". I'm not saying it's wrong (I've definitely seen bundles do it) - but it's the last place to look for a hook point. In you case, if it works well, I actually don't see any big issue with it :).
Cheers!
Hey erop
Interesting use-case, I'm glad that you could find the solution by yourself. Thanks for sharing it with others!
Cheers!
As soon as functional testing is involved having different *configurations* is needed, which likely means to rebuild the (whole?) container's cache between each test.
With only a dozen of tests bootstrapping the kernel, completing already takes minutes.
This makes me think of this issue https://github.com/symfony/...
Is there a way to either optimize by not creating a cache at all?
Or a way to selectively rebuild only some parts of the cache? (In my case only the configuration of one bundle changes over tests).
Hey @Raph!
Are you testing your application or testing a bundle that you will share, like we do in this tutorial? In this tutorial, indeed, you might find that you boot several different kernels with configuration. But these kernels are so small, they shouldn't take long at all to boot (and for the cache to be built). If you're testing your application (where, indeed, the container/kernel can become quite large), then that's different. However, in that case, you typically are booting your kernel just so you can fetch out services. And so, you typically are only booting your *one* kernel - and not rebuilding its cache.
I think your situation is maybe more the second... but with some special situation? You said:
> In my case only the configuration of one bundle changes over tests
Can you tell me more about that? Are you testing your application... but sometimes you boot your one kernel... with different configuration being passed to a specific bundle? I don't understand... so I bet I'm missing something ;).
Cheers!
It's a bundle and it can be configured. (An app' requiring it would do that from
config/package/my-bundle.yaml).I'd like to test multiple bundle's configurations especially since its configuration affects the services it provides. For example if
`driver1is set in the configuration, then it will create amybunde.Driver1service. It's not an uncommon pattern. For example the <i>qpush-bundle</i> creates oneQueue` service per configuration-defined queue : https://github.com/uecode/qpush-bundle/blob/master/src/DependencyInjection/UecodeQPushExtension.php#L63 (although I'm not sure it deserves being named the "Service Factoring" as in https://symfonycasts.com/screencast/phpspec/factory-class)In such a case, each test needs to bootstrap the kernel then clear its cache and in my case this takes 4 minutes for less 20 tests (30min for my testsuite on GitLab.com), almost exclusively dedicated to booting the kernel and cache-clearing afterwards. It'd run under 15 seconds with a static kernel (but most tests would then fail).
Hey Raph!
Yep, it makes perfect sense then - I would probably do something very similar. A few questions/points, however:
1) You shouldn't need to specifically clear the cache after. I mean, as long as you make sure that each kernel has its own "cache" directory (I often do this by setting some random "key" in the kernel's constructor, setting it on a property, and using it in getCacheDir), then nautrally each kernel will start pre-cleared because its cache will be empty. I'm not sure how much this will speed things up... but try it :).
2) Typically in a test like this (at least when you're creating a *truly* re-usable bundle - i.e. one that could be used in any Symfony app), the "kernel" that you're creating is very small - you usually are booting just a few bundles that your bundle depends on - for example - https://github.com/symfony/... - which just needs 3 bundles. In these cases, the kernel boots very fast - certainly in a few seconds... but probably under a second. Are you needing to boot many bundles / your entire app to run the tests?
Cheers!
Thinking further about this issue, my testsuite which boots the kernel many dozens of times, goes from ~40 seconds to >15 minutes... when enabling code coverage. Said another way: it's all about xdebug slowing-down kernel boot.
I fear this thread goes off-topic and I don't want to add to much unrelated noise.
(But I hope xdebug can be selectively disabled during phpunit kernel-boot process)
Closing that specific thread on a positive note: I switched to pcov (implying an update to phpunit 8) and disabled xdebug.
The testsuite with coverage runs in less than a minute (as fast as if no coverage was requested at all) instead of 15 minutes.
Hi,
The test fails ("x y" does not contain "stub").
It's not loading StubWordList, got "KnpU\LoremIpsumBundle\KnpUWordProvider" from the dump.
Any clue what I did it wrong?
I've made some changes on service.xml, but I think it does not affect the tests.
thanks
Hey Felipe L.
Did you register the StubWordList service on your testing kernel?
Where those the "X Y" comes from?
Sorry MolloKhan , ignore all this.
I forgot to remove the service from config.yaml, (lesson 2!)
I've removed and all works, rewrite services.xml as lesson 8 and all good.
thanks for you time anyway.
Ha! Nice, I'm glad to hear that you could fix your problem
And don't worry about this - everybody makes mistakes ;)
Hi MolloKhan ,
I've done some debugging and I'm not sure that my <i>CustomWordProvider</i> is working at all.
I had to change add some lines on <i>service.xml</i> in order to make the bundle works.
Latest version of that file, I got from Lesson 8 looks like this:
`
<service id="knpu_lorem_ipsum.knpu_ipsum" class="KnpU\LoremIpsumBundle\KnpUIpsum" public="true">
</service>
<service id="knpu_lorem_ipsum.knpu_word_provider" class="KnpU\LoremIpsumBundle\KnpUWordProvider" />
<service id="knpu_lorem_ipsum.word_provider" alias="knpu_lorem_ipsum.knpu_word_provider" public="false" />
<service id="KnpU\LoremIpsumBundle\KnpUIpsum" alias="knpu_lorem_ipsum.knpu_ipsum" public="false" />
`
Got the following error:
<a href="https://imgur.com/a/v22OLJQ">Cannot autowire service</a>
Then, I've made some changes and my service.xml looks like this:
`
<service id="knpu_lorem_ipsum.knpu_ipsum" class="KnpU\LoremIpsumBundle\KnpUIpsum" public="true">
</service>
<service id="knpu_lorem_ipsum.knpu_word_provider" class="KnpU\LoremIpsumBundle\KnpUWordProvider" />
<service id="knpu_lorem_ipsum.word_provider" alias="knpu_lorem_ipsum.knpu_word_provider" public="false" />
<service id="KnpU\LoremIpsumBundle\KnpUWordProvider" alias="knpu_lorem_ipsum.knpu_word_provider" />
<service id="KnpU\LoremIpsumBundle\KnpUIpsum" alias="knpu_lorem_ipsum.knpu_ipsum" public="false" />
<service id="KnpU\LoremIpsumBundle\KnpUWordProvider" />
<service id="KnpU\LoremIpsumBundle\WordProviderInterface" alias="KnpU\LoremIpsumBundle\KnpUWordProvider" />
`
After these changes, it works but I'm not quite sure that CustomWordProvider is being loaded properly.
If I added 'beach', following the lesson, the word does not appear on the page.
thanks
Felipe
Hi MolloKhan ,
when you say register, you mean on <i>registerContainerConfiguration</i>, right?
If it does, the method looks like this:
`
$loader->load(function(ContainerBuilder $container) {
`
thanks
Felipe
"Houston: no signs of life"
Start the conversation!