Problem/Motivation
Some test cases directly extend the class \PHPUnit_Framework_TestCase but they should extend UnitTestCase. It has a setUp() method that unsets the container in \Drual. The problem is that the service container is globally accessed in many places via \Drupal - if you don't clear the container in setUp() you might get unexpected test passes when your unit test grows bigger. Unfortunately at this point we need to accept that \Drupal is called in far too many places and we need to cope with that - by using UnitTestCase amongst other things. Otherwise you get into weird behavior with unit tests, and you only learn something after hours of debugging.
The disadvantage of using UnitTestCase is that it inadvertently covers \Drupal and FileCacheFactory. This means that coverage metrics aren't accurate (see also #2626832: Figure out how to check for unintentionally covered code). We can solve that by explicit @covers annotations on the test methods.
Proposed resolution
Use UnitTestCase as base class for all unit tests.
Remaining tasks
Discuss if there are other downsides if we use UnitTestCase everywhere.
| Comment | File | Size | Author |
|---|---|---|---|
| #11 | 2824313-11.patch | 3.54 KB | dawehner |
| #3 | 2824313_3.patch | 4.37 KB | mile23 |
Comments
Comment #2
klausiComment #3
mile23This is backwards. If I'm testing a unit that I expect should have no connection to
\Drupal, then using\PHPUnit_Framework_TestCaseinstead ofUnitTestCaseis part of the test. If something somewhere else breaks my test, there can only be three reasons:UnitTestCaseorKernelTestCaseor whatever.Since #3 is the most likely culprit, mandating the use of
UnitTestCasehides errors from us and prevents us from finally limiting\Drupalto it's intended uses.In fact, here's a patch that makes all changes necessary to get passing tests when you do
phpunit -c core/ --testsuite unitafter turningUnitTestCase::setUp()into an essential no-op. It's probably a better solution to #2626832: Figure out how to check for unintentionally covered code than the patch over there. Of course we can't really do that because of BC for contrib. Alas. :-)It demonstrates that things are better than we might think when it comes to the need for heavy-handed
\Drupalisolation.Setting NR only to kick off the testbot.
Comment #4
mile23Related: #2465727: Some unit tests don't call UnitTestCase::setUp()
See how this test is changed to explicitly show what it's testing? That's what we want everywhere.
Comment #5
tim.plunkettThis makes a LOT of sense!
This make no sense.
Either that second case needs a very clear comment, or I'm missing something...
Comment #7
dawehnerUnlike my previous assumption I believe using
UnitTestCaseI like to make that explicit. Could we maybe have a helper method for that?
Comment #8
klausi@Mile23: oh the irony, your patch shows that lots of testing blows up unintentionally.
It demonstrates that things are as bad as we think.
To be clear: if the outcome of this issue is a moderately small patch that demonstrates that we can use \PHPUnit_Framework_TestCase almost everywhere that would be fine with me. I'm a bit sceptical right now :)
Comment #9
mile23"here's a patch that makes all changes necessary to get passing tests when you do phpunit -c core/ --testsuite unit"
Note that's not the testbot. :-)
Check out the results:
1723 skipped tests because I don't have the yaml extension. The testbot reports 45 fails.
So 45 failed tests out of 1732. COMPLETELY INSURMOUNTABLE. :-)
Those fails all deal with the file cache prefix not being properly set, which I would call a bug in the file cache system, but no one else will because we can just sweep it under the rug in
UnitTestCase::setUp().That's not the point. The point is that our unit tests kind of suck because they rely on magic, and requiring
UnitTestCasemakes the situation worse because it's the magic part.Not everyone knows or cares what side effects are being introduced into their tests, and that's a problem. Because if someone does care, and uses
\PHPUnit_Framework_TestCaseas a base class for a considered reason, they'll be told they're doing it rong. And that will be me, and I'll end up re-writing this paragraph again.Comment #10
dawehnerCould we maybe provide a test listener, instead of relying on the base class, which does the
\Drupalcleanup?Comment #11
dawehnerHere is an approach for that.
Comment #12
mile23I'm not sure what problem #11 solves. I just want to be able to write a
\PHPUnit_Framework_TestCasefor core, and not useUnitTestCaseunless I think it's a good idea. Moving the setup to the listener just adds to the magic, in a way that I won't be able to avoid it, making the problem worse.My objection is to the rule that all unit tests should be
UnitTestCase, which is what this issue is about. By way of showing how it's not a good idea, I'm pointing out that tests themselves should be explicit in how they set up their dependencies and expectations. If we want to fix it so the tests are better, then we can do that in another issue.Also: We kind of have to keep
UnitTestCaseas it is for BC/contrib, unless we want to formally@deprecateit.Comment #13
mile23Related: #2825078: Deprecate UnitTestCase dependency on block module
Comment #14
neclimdulI'd sort of like to be able to use which ever base class I choose as well, I think requiring a specific base class is an odd arbitrary requirement and possibly a bit of a blocker for splitting libraries out of /core/lib. The magic cleanup might work but personally I feel like we should trigger errors similar to the way global state change errors/warnings if something was left in the global container.
That said, as the summary pointed out \Drupal::container and related proxy methods pepper Drupal and their addition in patches will likely lead to a convergence on most if not all test cases extending Drupal's base class... ¯\_(ツ)_/¯
Comment #16
klausiI'm convinced by #2866894: Component tests should not use Drupal\Tests\UnitTestCase but PHPUnit\Framework\TestCase that our components should be isolated from Drupal core dependencies, so usage of the PHPUnit base class makes sense. Closing this, sorry!