panther
panther copied to clipboard
[Question] Mocking container service in server process
Hi,
Is it possible to override/mock container services in the server process? I would like to mock an external data source in a functional test. I've tried the following, but this seems to override the service only in the client process container:
abstract class Test extends TestCase {
use PantherTestCaseTrait;
public function setUp() {
parent::setUp();
$this->client = self::createPantherClient();
}
public function test() {
$mockImplementation = $this->createMock(MyService::class);
static::$container->set('App\Service\MyService', $mockImplementation)
$crawler = $this->client->request('GET', '/some-url');
}
}
You can get it to work by defining an interface MyServiceInterface and dependency inject this interface into classes that use your service. By specifying different service implementations using yaml config for different environments is a solution, but it's not ideal. You would like to be able to directly mock the service from within the test case. Am I overlooking something here?
Thanks!
Got it working with this pull request: Web server options for environment variables + PANTHER_WEB_SERVER_DIR for custom index.php to call custom kernel which implements different test_cases.
To inject different testcases and configs in the server i use these 2 env vars: TEST_CASE & ROOT_CONFIG
Also used PANTHER_APP_ENV for custom environment to disable authentication since i could not inject cookie succesfully.
@ThomasTr, was the PR ever merged? I am trying your suggested approach, and it doesn't seem to me working.
Hi @arderyp, not in this PR but these two PR`s where merged: https://github.com/symfony/recipes/pull/464 https://github.com/symfony/panther/pull/99
Now you can use:
static::$options['env']['CUSTOM_PATHER_VAR'] = 'your content';
$client = static::createPantherClient(static::$options);
HTH, Regards, Thomas
hey @ThomasTr thanks for the quick response. is static::$options your own custom convention? I see no static::$options defined on PantherTestCase. I see static::$defaultOptions, on the other hand.
I suppose I can just pass in the options though and don't need to set the static property, correct?
@ThomasTr, I tested
$this->client = static::createPantherClient([
"env" => [
"CUSTOM_PATHER_VAR" => "this is a test",
]
]);
This works. Unfortunately, it looks like you can only set string values. For example, the following leads to a Object of class stdClass could not be converted to string error:
$this->client = static::createPantherClient([
"env" => [
"CUSTOM_PATHER_VAR" => new \stdClass(),
]
]);
Consequently, I don't see how you achieved your goal of passing a mocked service through this mechanism (source):
$this->options['environmentVariables']['TEST_CASE'] = $testCase;
Given the original request here, how exactly did you pass a mocked object through an environment variable?
I, like you and @mwarnaar, am trying to mock an external API data source. I've achieve this in Web test cases using the exact method @mwarnaar mentions in his OP. I don't see how the dots connect here.
Are you using serialization/deserialization to convert the mocked object created in the Panther test to a string in order to pass it through an env var to the webdriven client?
Are you not passing an actual mocked service through the env var at all? Maybe you are just passing a simple flag to indicate to the kernel that the data source needs to me mocked... if so, how then do you mock the data source? You build a PHPUnit style mock object in the Kernel itself? You use some config voodoo to somehow override methods on the real service object?
I'd really love a more thorough explanation of how you are achieving @mwarnaar's stated goal.
EDIT: I just tested serialization. While it would work in theory, I am running into issues unserializing the object on the other end... sigh.
Unfortunately, it looks like you can only set string values.
Thats exactly how environment variables work: https://symfony.com/doc/current/configuration.html#configuration-based-on-environment-variables - "The values of env vars can only be strings, but Symfony includes some env var processors ( https://symfony.com/doc/current/configuration/env_var_processors.html ) to transform their contents (e.g. to turn a string value into an integer)."
Given the original request here, how exactly did you pass a mocked object through an environment variable?
You shouldnt try to do this. It seems like bad hack.
Hey, @arderyp for sure, you can just pass the options as you have done in your second anwser. It was just a snippet from my own testcase which extends PantherTestCase.
As @domis86 already mentioned while typing my anwser for you: You should not do this. My approach is to use testCases as symfony does it in its own tests. You need a custom test kernel for it similar to: https://github.com/symfony/symfony/blob/60ce5a3dfbd90fad60cd39fcb3d7bf7888a48659/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php
In your testCase you can overwrite configs, services or use mocks...
Thanks @domis86 and @ThomasTr! I too agree that passing a mocked object through $_SERVER seems hackish and wrong.
I appreciate the AppKernel link @ThomasTr. Is there documentation on this $testCase option? I see you've referenced it in your code snippits, and I see you've mentioned creating a custom testing Kernel. What I am still unclear about is how you go about using proper mocks (as in, instances of PHPUnit's MockObject) in your custom test Kernel, as you instructed:
In your testCase you can overwrite configs, services or use mocks...
I don't think my case will be achievable with simple config overrides.
Thanks again for the assistance, I really appreciate it.
Hey, just look around the linked Kernel, they have done the same in their tests.
I've tried my best to follow it @ThomasTr, but the code is not type hinted or commented so all I can really tell is that $testCase is just a subdirectory with separate config files, bundles.php. There is nothing here that I'm seeing about generating/using proper PHPUnit MockObjects.
Here is how the test AppKernel is being used: https://github.com/symfony/symfony/search?q=AppKernel
I'm wondering if maybe you and I just have different interpretations of the word "mock", and that's causing confusion. I don't mean to simply provide a custom set of configurations and kernel and bundles.php to overwrite the live versions of these files. I mean to generate PHPUnit MockObject versions of the services and inject those into the container, which cannot, as far as I know, be achieved with a config file: https://phpunit.readthedocs.io/en/9.5/test-doubles.html
It's quite simple to do outside of Panther: https://dev.to/nikolastojilj12/symfony-5-mocking-private-autowired-services-in-controller-functional-tests-24j4