Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem
\Drupal
is a static global state construct, which can leak into unit test environments, in case a previous test could not be completed successfully.
Proposed solution
- Allow testing frameworks to explicitly unset any possibly existing container in
\Drupal
by calling\Drupal::setContainer(NULL)
.
Comment | File | Size | Author |
---|---|---|---|
#30 | drupal8.phpunit-setup-drupal.30.patch | 1.46 KB | sun |
#26 | drupal_2171315_26.patch | 1.22 KB | Xano |
Comments
Comment #1
dawehnerComment #2
tim.plunkettIt wasn't tagged novice when I first loaded the node!
Comment #3
tim.plunkettI'm really not a novice, I swear
Comment #4
dawehnerHAHA, you know that I told you of the novice part before even pasting the URL.
What about doing the following to just be 100% sure:
Comment #5
diarmy CreditAttribution: diarmy commentedHi guys, I followed the suggestion in #4 and added in the if statement, whilst proudly wearing a novice's cap, of course. :)
Comment #6
dawehnerThank you!
Comment #7
webchickThis looks like a nice DX improvement. Welcome to the core queue, diarmy. :)
Committed and pushed to 8.x. Thanks!
Comment #8
XanoSomehow this breaks the PHPUnit tests of Payment and Currency when those are run through the UI. Command line execution of the tests works fine.
The problem manifests itself in the form of the following error message that is displayed directly after the test execution batch is started:
batch.storage
service, which makes me believe this is a problem with the test runner, maybe evenUnitTestCase::tearDown()
resetting the host site's container?Comment #9
XanoThis is the same approach that TestBase takes, and to me it looks much more sane.
It will also likely fail, because I haven't seen any PHPUnit test case yet that calls
parent::setUp()
...Comment #10
dawehnerOh, yet another sideeffect by using phpunit inside a running drupal installation (simpletest). It is just wrong in general.
I wonder whether we really need a clone, have you tried it?
Comment #11
XanoI assume we do not need to clone the original container. I have not tested it, as I knew TestBase had already solved this problem and figured that taking a similar approach would be good.
Comment #13
XanoNo more cloning, a check to see if there even is an original container, and setting an empty new container if that's the case.
Comment #14
sunAny chance to move this code completely out of the PHPUnit test classes and instead into the responsible function in simpletest.module that wraps the invocation of phpunit?
As a @dawehner already mentioned, phpunit tests are normally executed from the command line. Only the Simpletest/UI wrapper needs this additional futzing with globals.
Thus, instead of making our phpunit tests ugly, we should move the ugliness into the single spot that is simpletest.module.
Comment #15
sunHow can a phpunit test futz with the simpletest test runner environment, if phpunit is executed in a separate shell process?
https://api.drupal.org/api/drupal/core!modules!simpletest!simpletest.mod...
Looking at the originally committed patch in #5, its one and only purpose seems to be to reset \Drupal to nothing, so as to prevent a previously container in \Drupal to leak into a subsequently executed test.
As long as classes covered by PHPUnit tests depend on the global state construct that is \Drupal, that operation actually belongs into setUp() or perhaps even setUpBeforeClass(), because we have to ensure a clean environment before a test is executed, not afterwards.
Comment #16
sunThese words in code.
Comment #18
tim.plunkettWell that won't work because it requires something that satisfies the typehint.
And passing a new empty container will pollute subsequent tests even more.
Comment #19
dawehner13: drupal_2171315_13.patch queued for re-testing.
Comment #20
sunUgh, PHP core logic strikes back.
Comment #21
dawehnerEhem, don't we still want to kill the container at the end of the testrun?
Comment #22
suntearDown()
is irrelevant and not necessarily reached (in case of an exception or whatnot).Architecturally, that's the essence of the patch in #13 - all it tries to ensure is that the global state container in
\Drupal
of a previous test does not leak into a subsequently invoked test.But that's the wrong way of thinking about global state in tests. If you have to deal with global state in the first place, then instead of fragile cleanups after a test is executed, you ensure that no global state exists before a test is executed.
Lastly, any idea of
$this->original
has no place in PHPUnit tests. There is no Drupal environment. As a consequence, there is no "original."Does that clarify the situation?
Comment #23
sunThe currently proposed patch will also help with #2196241: Remove string translation services from TestBase container
Both issues share the same requirement: Allow the testing framework to ensure that there is no container in \Drupal.
Comment #24
sun20: drupal8.phpunit-setup-drupal.20.patch queued for re-testing.
Comment #25
dawehnerYes it does, indeed.
Comment #26
XanoEither NULL should be documented, or we should use a reset method, which I think is better DX.
Comment #27
sunNot sure whether DX is really relevant here, since this is not supposed to be called by any code other than the testing frameworks.
Comment #28
dawehnerIt is not something we should consider as a real part of our API, they are just workarounds for testing.
Comment #29
alexpottI prefer the patch in #20 - adding a
resetContainer()
method to \Drupal is very confusing - as it does not reset the container it obliterates it. But I also agree that the docs of setContainer(NULL) could be improved to explain why one would ever call it with NULL.Comment #30
sunAdded docs. Added proper issue summary.
Comment #31
Berdir30: drupal8.phpunit-setup-drupal.30.patch queued for re-testing.
Comment #32
dawehnerBack to RTBC
Comment #33
alexpottComment #34
catchCommitted/pushed to 8.x, thanks!