Problem/Motivation
When writing tests it would be helpful to use the test classes to run against an installation of Drupal created by means other than the test setup. When writing functional tests and integration tests for whole-site builds and not specific features isolated to specific modules, the approach to writing tests has typically been:
- Set up a functioning version of the website on CI by either importing a database from another environment (or reinstalling and importing config).
- Create a base test class which overrides setup to halt the installation of Drupal.
- Write tests which all work against the same, single environment setup on CI that closely reflects the production environment.
Alternatives to this are writing the site as an installation profile that can be used for each test or importing the websites configuration folder during every test run. The issue with these two approaches is how slow they are and missing out on creating a test environment that exactly represents production, beyond just the configuration and code.
Examples of things which impact a site during runtime are the sites content, all configuration created/available for modification on production and the database schema (a site having gone through every updb as opposed to a fresh module installation). Once an environment is replicated on CI, tests of this kind are impacted by these factors, making them useful for determining the real status of a site.
Proposed resolution
Add a property to BrowserTestBase which forgoes the installation of Drupal, sets the test class up to run against the parent site and cleans up entities created during the test.
Remaining tasks
- Seek approval for this feature. See #68
- Ensure the entities are cleaned up automatically. #2551893: Add events for matching entity hooks
- Make sure approach is compatible with and possibly blocked by #2796105: Move similar methods in BrowserTestBase / WebTestBase to a trait; untangle installDrupal().
User interface changes
None.
API changes
Additional properly on BTB that controls the Drupal installation status.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #99 | 2793445-8-9.patch | 15.35 KB | jibran |
| #98 | 2793445-8.8.patch | 15.3 KB | sam152 |
| #96 | btb-8-7-96.patch | 15.32 KB | larowlan |
| #95 | 2793445-95.patch | 15.17 KB | jibran |
Comments
Comment #2
sam152 commentedComment #3
dawehnerNote: github.com/dawehner/sitetestbase and https://www.drupal.org/node/2793443
Comment #4
dawehnerIMHO ideally we would have a flag to make this possible. A flag which is controlled by the test class itself. In the case this flag is set, it would setup the right database credentials by loading it from settings.php and call it a day.
Comment #5
sam152 commentedSite test base looks great, however one thing to consider is being able to extend either JTB or BTB and still use this feature. For that reason it probably makes sense for this to live in the guts of BTB and be toggled with a protected property or something similar.
Edit: read my mind :)
Comment #6
larowlanAdapted from our base class
No idea how to test this.
Comment #7
dawehnerWouldn't it be nice if we would have some entity subscriber which tracks all created entities automatically?
Comment #8
benjy commentedWe do the check in cleanupEnvironment() anyway, can't we just unconditionally add the entities into $cleanUpEntities?
Comment #9
benjy commentedThis was all I needed to actually make this work, didn't look at any of the other comments.
Comment #10
sam152 commentedI've been using this patch and it seems to be pretty good. I did run into a problem with one of the assertions in drupalLogin. It makes the (previously true) assumption that the session name will always be based on the database prefix. When running against an installed site drupal_valid_test_ua is not set or used so the logic in the session_configuration service which determines the session name correctly ignores the database prefix. For this reason BTB can no longer make that assumption about the session name and should instead just call out to session_configuration to figure out what the session name is.
Patch attached, no idea if this will pass.
Comment #12
sam152 commentedApplied against 8.2 locally. Whole thing probably needs a reroll for 8.3.
Comment #13
sam152 commentedComment #14
sam152 commentedReroll.
Comment #15
sam152 commentedJust in terms of moving this issue forward, does it fall under the phpunit initiative? Can we get an in-principle approval of the concept before spending more time making this work for core?
Comment #16
dawehnerIMHO this totally falls into it
Nice patch size.
I would totally like that as a feature for sites testing. I know at least frankcarey would be also highly interested in the feature in general.
Regarding approval or not, I think it would help to provide some concrete example in the issue summary.
On top of that it would be nice to maybe open up a follow up to deal with the problem of automatic cleanup (somehow listen to all created entities and revert them).
Comment #17
sam152 commentedUpdated issue summary to reflect how this feature would be used. High on the todo list is seek approval for this feature, beyond that I think we'd be able to handle the clean-up feedback as part of this issue.
Comment #18
sam152 commentedComment #19
dawehner@Sam152
To be honest I have no clue who would have to approve this feature.
Comment #20
sam152 commentedI nominate you for approval, as one of the leaders of the phpunit initiative :)
Comment #21
benjy commentedBit of clean-up here, the only way I can see us automatically picking up the created entities would be via hook_entity_update/insert but that would require a module enabled in every test. Once #2551893: Add events for matching entity hooks is in we could register an event listener in the test setup much easier.
We could also add the already installed check in markEntityForCleanup if memory usage is a concern in bigger tests.
Comment #22
chx commentedI have a different approach to this problem which I found to be fast and effective at rooting out bugs: I use KernelTestBase and a test module. The test module has a config/install directory which contains symlinks to files in the config sync directory of the live site. This allows to recreate a fraction of the live site precisely and quickly. The test are runs very quick since it doesn't need to fully bootstrap Drupal even once. It also allows me to mock anything that needs mocking.
Comment #23
sam152 commentedSounds like a great way to get equivalent behavior for kernel tests. Does the same technique help with functional testing at all?
Comment #24
chx commentedThis is functional testing. I know it's tangential to the BTB -- but unless you are JS testing, this is functional testing. A bit crazy but real, just don't forget to enable all the modules.
Comment #25
sam152 commentedRan into some issues running JTB with this setup. Some of the site variables weren't setup making mink fail due to not having a writable temp directory.
I'm hoping once #2796105: Move similar methods in BrowserTestBase / WebTestBase to a trait; untangle installDrupal() drops, we can have another look at this without wasting time moving forward knowing a very disruptive change is coming down the line. Hopefully we can eliminate some of the mess this patch has created easier once that's in.
I also have a version rolled against 8.2.x for reasons.
Comment #27
sam152 commentedComment #28
frankcarey commentedYes, per Daniel, I'm very interested in this especially where we can the same tools for testing between core and individual sites. (and hopefully across Behat / phpsec/ BTB / etc)
One thing we're doing in the (new) TestDrupal behat extension is keeping track on any entities that are created so that we can:
The problem is that we're only keeping track of the entities that are created in behat steps.. we don't actually know if any other entities are created. I'm at BADCamp right now and I'm working on a new module called testdrupal_helper that among other things will map entity creation to new symphony events as was mentioned above. I'll let you know how that comes along and will post some code for some feedback.
Comment #29
pfrenssenThat's not something you should do or even care about in Behat test scenarios. Behat is about BDD, it is intended to describe user behaviour. If the website creates any entities behind the scenes which the user doesn't know about then your Behat scenario should not deal with this. In short, if you are using Behat to write functional tests instead of describing user behaviour scenarios you are not using it correctly. You should use BTB for functional tests.
Where this will all fall apart is when you have 2 tests running simultaneously against the same installation. BTB will be able to handle this fine. Testdrupal_helper won't. The best way to solve this in practice is to use unique labels for your entities in every Behat scenario and add some steps at the end to find the entities that were created manually the test by label and clean them up.
Comment #30
pfrenssenI'm not sure if this pattern of overriding all traits that create entities is going to be practical, especially for contrib and custom tests. For core tests it will probably be fine, but what happens if a contrib module defines a bunch of traits too for creating their entities? We won't be able to override them in BTB, so this means that BTB should be subclassed. This will get messy fast when you are writing a test for something that involves multiple contrib modules.
Maybe we should add support for entity cleanup in the traits themselves. Traits are intended to be dropped in to tests when needed, and ideally without requiring any local overrides.
Inside the traits we can detect whether we are running a test that uses a persisting database, and call
$this->markEntityForCleanup()in it.I think the cleanest solution would be to make BrowserTestBase implement a new
PersistentTestDatabaseInterfacewhich declares themarkEntityForCleanup()method so we can use this to detect support for entity cleanup in the traits:Comment #31
sam152 commentedThe interface idea sounds interesting. Would you implement the interface, use the trait and then implement no other methods?
It's also worth noting you don't always have an instance of the entity that has been created. Submitting a form doesn't return the entity for example.
Comment #32
jibran#2796105: Move similar methods in BrowserTestBase / WebTestBase to a trait; untangle installDrupal() is in. NW as per #25.
Comment #33
pfrenssenIf any entity is created through the UI then it will need to be cleaned up manually at the end of the test. I don't think there is a reliable way to automate this.
Comment #34
sam152 commentedI think dawehner's suggestion of having an event subscriber or entity lifecycle hook to track these would be viable.
Comment #36
frankcarey commented@pfrenssen , I think you are mostly right in saying ".. if you are using Behat to write functional tests instead of describing user behaviour scenarios you are not using it correctly." However, say you have an entity that when created, creates other entities.. (commerce comes to mind). When you need to do your cleanup after a scenario, it's very hard to track all of these things that need to be deleted if you don't have some way to hook in. Your suggestion to use a specific "label" may work in some cases, but not all. The alternative is doing full reinstall or to revert the database to a previous dump which often takes longer than necessary. Also the @Given steps, which prepare the situation to be tested, certainly don't have to be done using user behavior and often that isn't an efficient way to go anyways.
You said, "Where this will all fall apart is when you have 2 tests running simultaneously against the same installation.", but Behat doesn't allow for simultaneous tests anyways. The way we run tests in parallel is to have completely separate environments and run a subset of the features/scenarios in each. That said, there may be a way to make this more feasible.. perhaps a header in the request or in the session that sets which environment we're dealing with?
I agree with @Sam152 "I think dawehner's suggestion of having an event subscriber or entity lifecycle hook to track these would be viable." I don't think it's necessary to pollute things with markup like '$this->markEntityForCleanup($user);'.. I haven't made any progress with that module since BADCamp, but I'll post here if I start working on it again.
Comment #37
benjy commentedHere's a re-roll against 8.4
Comment #39
benjy commentedHere's a first attempt at using an event listener to clean-up in the tests, depends on #2551893: Add events for matching entity hooks and the current EntityCleanup tests needs to be run against an existing installed site, I was using Standard locally. Not sure if we can make it run on the bot yet.
Also the original patch re-rolled against 8.4.x with a fix from a a mistake i made in the re-roll.
Comment #41
benjy commentedOriginal patch re-rolled for 8.3.0, anyone want to review?
Comment #43
pfrenssenMy remark from #30 is not yet addressed.
Comment #44
benjy commentedMoving the checks to the traits makes sense if that was our end goal but I still think a better solution is what I proposed in #39 because that will pick up all entities that were created in a generic way. However, it's dependant on #2551893: Add events for matching entity hooks and who knows how long that issue will take.
The problem with moving the methods to the traits is, we have to then support clean-up in the future for people using those traits, which isn't so easy if we move to an event listener which would be registered once for all entities, not based on the traits you were using.
Comment #45
pfrenssenOK I agree, I proposed to use the separate interface and the traits as a way to fix the current state of the patch, but if we can make it work automatically then it's even better, so that's definitely worth exploring.
I have been thinking about the event listener and we can probably identify entities created during the test by associating it with the test prefix from
drupal_valid_test_ua(). We cannot use semaphores or a similar time based approach because it would also catch any other entities created outside of the test while it is running.Comment #46
webflo commentedFixed the remaining failures.
Comment #48
webflo commentedComment #49
webflo commentedThats the only change i made, drupalCreateRoleParent returns only the role id. Not the entity.
Comment #51
yogeshmpawarComment #52
yogeshmpawarRe-rolled the patch against 8.4.x branch because it's failed to apply.
Comment #53
hchonovWith the current patch the modules defined under $modules will not be installed. Should we forbid having listed any modules if running against the current installed Drupal and throw an exception in such a case?
Comment #54
mile23Just discovering this issue...
I think it's a bad idea to test without a fixture. It's like doing dev on production. But it might also be good to have a more formalized Drupal alternative to Behat or other systems. Generally -1 from me.
But since it's +1 from everyone else: Make it a new base class that extends from BTB. That way you're not always making the test framework more complex with a lot of
if ($this->runAgainstInstalledSite)special cases, and we're not promising that BTB will work this way forever. Plus it's much easier to document.Also:
Use
$this->container->get('app.root')becauseDRUPAL_ROOTis pure evil.Comment #55
jibran\Drupal\simpletest\UserCreationTrait::createAdminRole()creates\Drupal\simpletest\UserCreationTrait::createRole()directly so we have to addto the
\Drupal\Tests\BrowserTestBase::drupalCreateUser().Comment #57
sam152 commentedRe #54, the only issue with that is support for both BTB and JTB. We can't easily compose test classes with different drivers, so we're sorta stuck building it into the test base.
Comment #58
jibranYet another reroll also address #55.
Comment #59
jibranComment #60
hchonovWhat if the role actually already exists on the currently installed instance? In this case this will delete an existing role, not created in the test.
Comment #61
jibranThat is not true. There is no way to pass the current role to
\Drupal\Tests\user\Traits\UserCreationTrait::createUser. Instead, you can do thisComment #62
hchonovWhen executing this method yes, but one might override the method drupalCreateUserParent() and add existing roles to the user entity instead of creating new ones. An other option is that some of the functionality of UserCreationTrait is being overwritten. You never know what a developer might implement, therefore we should not depend on the implementation underneath, but mark an entity for deletion only if we are sure that we've created it during a test, and the best place for this is where we call $storage->create().
Has it been considered to mark entities for deletion in hook_entity_create() during tests instead? This will cover each entity, which is not currently done by the patch, and reduce the complexity as well.
Comment #63
hchonovOh, I just saw that @frankcarey has been talking on something like this.
Comment #64
jibranWell, this is true for all the methods using
markEntityForCleanup.This is a stop gap approach till #2551893: Add events for matching entity hooks is in.
See the patch in #39
Comment #65
mile23OK, so then turn JTB into a trait. It's small.
Make this class heirarchy: BTB -> PreExistingBTB -> PreExistingJTB use JTBTrait.
Also we should add a test listener that fails a test run if core tests use these pre-existing fixtures.
Comment #66
acbramley commentedReroll for 8.4.0
Comment #67
sam152 commentedComment #68
jibranI think before moving forward we need to ask the subsystem maintainer and framework manager about their opinion.
Comment #69
mile23I'd much rather see a way to make a repeatable fixture site based on config and some entities in a dehydrated form, rather than an open-ended extension to BTB that lets you un-isolate a functional test by design.
Having a more behavioral-oriented framework (which is what this is) separate from BTB would also be useful. That's why I raised concerns in #54 (and #65).
Comment #70
larowlanI think we should get back to forward porting the improvements done to simpletest in D7 first.
#2796105: Move similar methods in BrowserTestBase / WebTestBase to a trait; untangle installDrupal() was the first step for that.
#2747075: [meta] Improve WebTestCase / BrowserTestBase performance by 50% is the meta.
#2795749: [Testing issue] Make tests faster was the last patch.
When those are in, this might be moot - and we'll retain the isolation
Comment #71
larowlanComment #72
jibranYou mean #2788777: Allow a site-specific profile to be installed from existing config?
Unfortunately, #2747075: [meta] Improve WebTestCase / BrowserTestBase performance by 50% has been stalled for more than a year now :(
I'm confused, how can performance improvements help us allow running BTB test to an existing site?
Comment #73
dawehnerI think all @larowlan is saying: We might not really need the functionality if our tests become blazing fast.
Comment #74
larowlanThanks @dawehner, that's what I meant :)
Comment #75
Anonymous (not verified) commentedYep, but looks like @jibran is saying: performance is cool, but in this issue we want to get a simple opportunity to check our raw ideas on suitable sites. Even if such tests are run longer, it does not matter.
By the way, about performance, here is one more #2900208: Improve performance of functional tests by caching Drupal installations :)
Comment #76
jhedstromI think something like this would still be really great to replace Behat for sites (not core testing). As things stand now, custom code can be tested for sites using our phpunit-based testing framework. This breaks down though when the tests need to rely on a site's entire configuration (eg, display modes, access, etc), rather than a small subset of modules.
Currently, Behat fills this gap, but if used in a non-BDD manner, it's not really being used correctly. It still continues to be used though because tests can be run against a completely installed site.
That's not to say this need be in core--it could be a contrib module trait or something that allows tests to swap the test environment...
Comment #77
dpiUpdated patch #66 for 8.4.4
Comment #79
jibranYet another reroll
Comment #80
jhedstromAny thoughts re: #76 as a response to the above concerns?
With these changes in core, a contrib project could pick up from here and allow for re-usable entity cleanup by extending common core traits such as
NodeCreationTraitand tracking created nodes as the Drupal Behat Extension does now.Comment #81
mile23Please turn this into a trait that doesn't require any changes to BTB and is not used by BTB.
That way when you want this behavior you subclass BTB and then use the trait, and no one has to go hunting for a variable being set somewhere.
Comment #82
jhedstromMinimizing the needed changes to BTB and JTB would be nice, especially since core itself will never use any of this. We just need to make the changes necessary so an external library is able to utilize these classes to test existing sites I think.
Since core will never user any of this, I think they could be moved to an external library as a handful of traits that use the core traits, something like:
The tracking of cleanup entities (and the removal) could be another trait in that external library.
Comment #83
mile23+1
Comment #84
jibranWe need to re-roll 2793445-39-do-not-test.patch from #39 which depends on #2551893: Add events for matching entity hooks. Once that's done everything in #82 will be addressed.
I think this should be a separate base class whether it should live in core or contrib it is another discussion,
I disagree with this assumption. We have an experimental profile in core now with default content and blocks. I think the testing of those things will hugely benefit from this feature.
If we create a new trait and inherit a new base class from BTB and use the new trait in it then PHP 5 will not understand which
::prepareEnvironmentshould be used, the one from\Drupal\Core\Test\FunctionalTestSetupTraitor the one from new trait. Therefore, I suggested above that this should be a separate base class.Comment #85
jibranReroll after #2775653: JavascriptTests with webDriver
Comment #86
moshe weitzman commentedThis patch adds an mkdir for the writing of the htkey file. This is the same as #2246725: Make sure TestSitePath exist before creating .htkey but since that issue is closed, we include it here.
Comment #87
moshe weitzman commentedUgh. Again
Comment #88
Mixologictriggerin' the testin'
Comment #89
mile23Still doesn't address #81.
Docblock is now incorrect.
prepareEnvironment()is now 103 lines of code, which is one of the reasons this should be a separate base class or a separate trait.Please turn the
elsesection into another method called something likepreparePreExistingEnvironment()instead of adding complexity, and flip the logic onrunAgainstInstalledSite.Comment #90
jibranJust discovered https://github.com/weitzman/drupal-test-traits/. Nice one @moshe weitzman. I think we can 'won't fix' this as soon as https://github.com/weitzman/drupal-test-traits/issues/2 and https://github.com/weitzman/drupal-test-traits/issues/3 are addressed.
Comment #91
benjy commentedAlso, the traits in that project don't handle automatically marking created users, node, blocks etc for clean up?
Comment #92
mile23We could also add @see to BTB here, pointing to that repo.
Comment #93
moshe weitzman commentedI hope folks use and contribute to https://github.com/weitzman/drupal-test-traits/. I think its a solid alternative to this issue. My intent is to hit a sweet spot where the project is useful and still lean.
I agree that this issue can be closed.
Comment #94
sam152 commentedI'll do the honours :)
Comment #95
jibranFor people like me who are still using this patch with 8.6
Comment #96
larowlanFor people like me who are still using this patch with 8.7
Comment #97
didebruThanks @larowlan!
Comment #98
sam152 commentedReroll for those still using the patch on 8.8.
Comment #99
jibranReroll for those still using the patch on 8.9.
Comment #100
steinmb commentedMoshe did not get a lot of traction and archived the repo. back in 2018. More up to date activity here. Re-opening.
Comment #101
dpiThe project moved to Gitlab -> https://gitlab.com/weitzman/drupal-test-traits
Can vouch the project works and is well maintained.
Comment #102
steinmb commentedAh, thank you :)