Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch the order in which @before methods and setUp() are executed #1616

Closed
beberlei opened this issue Feb 14, 2015 · 34 comments
Closed

Switch the order in which @before methods and setUp() are executed #1616

beberlei opened this issue Feb 14, 2015 · 34 comments
Labels
type/backward-compatibility Something will be/is intentionally broken
Milestone

Comments

@beberlei
Copy link
Contributor

The methods that are tagged with @before are called after the setUp() method.

That makes it impossible to use traits for some generic setup code and then use the already existing services from the generic setup to do a specialed setUp, example:

class MyTest extends PHPunit_Framework_TestCase
{
    use ContainerSetup;

    public function setUp()
    {
           $this->container->get('..'); // if ContainerSetup has a `@before` hook, its not called here yet.
    }
}
@TomasVotruba
Copy link

I would love this feature too.

👍

@sebastianbergmann
Copy link
Owner

Can you send a pull request, @beberlei? I think this can go into 4.5.

@Dr-Head
Copy link

Dr-Head commented Feb 27, 2015

👍 agreed

@giorgiosironi
Copy link
Contributor

I want to open a PR for this. Some questions to avoid implementing the wrong thing:

  1. what should be the behavior for @after? By simmetry it should be called after tearDown()?
  2. what should be the behavior of @beforeClass and @afterClass? Will they follow the same pattern of being called respectively before and after setUpBeforeClass() and tearDownAfterClass()?

@chungth
Copy link

chungth commented Jul 28, 2015

+1

2 similar comments
@Thijmen
Copy link

Thijmen commented Aug 15, 2015

+1

@deeky666
Copy link

+1

@KingMob
Copy link

KingMob commented Sep 23, 2015

+1 @giorgiosironi what's the status of PR 1722? I see it failed a CI check.

@giorgiosironi
Copy link
Contributor

It failed the run on PHP 7 which was unstable at the time, no test failure on current versions of PHP.
My questions at #1616 (comment) stills stand to define some other behavior so that I can finish it.

@sebastianbergmann
Copy link
Owner

I don't know what the right behavior would be, @giorgiosironi. Mostly due to the fact that I don't use @before, etc.

@ghost
Copy link

ghost commented Oct 12, 2015

+1

@sebastianbergmann
Copy link
Owner

This should do what you propose, @beberlei, right?

diff --git a/src/Util/Test.php b/src/Util/Test.php
index 1dcc921..28594de 100644
--- a/src/Util/Test.php
+++ b/src/Util/Test.php
@@ -806,19 +806,31 @@ public static function getHookMethods($className)

                 foreach ($class->getMethods() as $method) {
                     if (self::isBeforeClassMethod($method)) {
-                        self::$hookMethods[$className]['beforeClass'][] = $method->getName();
+                        array_unshift(
+                            self::$hookMethods[$className]['beforeClass'],
+                            $method->getName()
+                        );
                     }

                     if (self::isBeforeMethod($method)) {
-                        self::$hookMethods[$className]['before'][] = $method->getName();
+                        array_unshift(
+                            self::$hookMethods[$className]['before'],
+                            $method->getName()
+                        );
                     }

                     if (self::isAfterMethod($method)) {
-                        self::$hookMethods[$className]['after'][] = $method->getName();
+                        array_unshift(
+                            self::$hookMethods[$className]['after'],
+                            $method->getName()
+                        );
                     }

                     if (self::isAfterClassMethod($method)) {
-                        self::$hookMethods[$className]['afterClass'][] = $method->getName();
+                        array_unshift(
+                            self::$hookMethods[$className]['afterClass'],
+                            $method->getName()
+                        );
                     }
                 }
             } catch (ReflectionException $e) {

@sebastianbergmann sebastianbergmann added the type/bug Something is broken label Oct 14, 2015
@sebastianbergmann sebastianbergmann added this to the PHPUnit 5.1 milestone Oct 14, 2015
@sebastianbergmann
Copy link
Owner

As discussed with @beberlei, only the order for @before and @beforeClass must be changed.

@GrahamCampbell
Copy link
Contributor

👎

@GrahamCampbell
Copy link
Contributor

This breaks every single Laravel application.

@taylorotwell
Copy link

This is indeed a breaking change.

@sebastianbergmann
Copy link
Owner

I do not consider this a breaking change. The behavior was undefined before, now it is defined. You were relying on undocumented behavior, sorry.

@GrahamCampbell
Copy link
Contributor

While the order of the before annotations was not defined, the fact they were after the setup method, was defined. This is breaking.

@marcioAlmada
Copy link
Contributor

@GrahamCampbell could you explain how this "breaks every single Laravel application"?

I wonder if projects that use phpunit as a dependency, like codeception, would have issues too.

@GrahamCampbell
Copy link
Contributor

Everyone will now get a fatal error because the setup method created an object that before methods then used.

@MadaraUchiha
Copy link

@GrahamCampbell That's not how breaking changes work. If you rely on undocumented, untested behavior, nothing is set in stone.

@GrahamCampbell
Copy link
Contributor

I don't see how the actual details matter though. It's a breaking change, if you consider it's behaviour to have been defined.

@GrahamCampbell
Copy link
Contributor

Just because something's undocumented, doesn't mean you should break it. There's plenty other stuff not documented that people would be unhappy about if it broke.

@GrahamCampbell
Copy link
Contributor

That's an example of the mess this makes: https://travis-ci.org/StyleCI/StyleCI/jobs/94697689.

@taylorotwell
Copy link

Yeah, to me a breaking change is defined as something worked before and now it doesn't. That's "breaking" - as in you broke applications. Is that not a good definition of "breaking"?

@Ma27
Copy link
Contributor

Ma27 commented Dec 7, 2015

why not making the behavior configurable through the phpunit.xml?
We could release a phpunit 5.1.1 which fixes that and in phpunit 6 the old behavior can be dropped as breaking changes can be done in major releases according to semantic versioning.

@sebastianbergmann
Copy link
Owner

@Ma27 PHPUnit already has way too many configuration settings, so no on that.

@MadaraUchiha
Copy link

@taylorotwell No, if you rely, for example, on the existence of an internal global variable, and you use it for your purposes, and someone refactors that global variable away, it is not a breaking change.

Breaking changes are changes to the documented, tested, defined, public API. If you rely on behavior that only incidentally works from the way the code was written, but that behavior was not defined or tested, defining/testing/changing it does not constitute a breaking change.

Things don't need to be explicitly defined to be undefined, in order to be undefined.

sebastianbergmann added a commit that referenced this issue Dec 7, 2015
This reverts commit 8cb498a.
@taylorotwell
Copy link

I'm going to look at this in a few minutes and see how hard it is for us to be agonstic about what order these things are run in.

@sebastianbergmann
Copy link
Owner

@taylorotwell The change has been reverted for PHPUnit 5.1 and was rescheduled for PHPUnit 6. If you want to look at something today ... why not look at laravel/framework#10808 ;-)

@taylorotwell
Copy link

Just wanted to note that on the Laravel side we insulated ourselves from this change. Not sure if that effects you wanting to go ahead and put this in a 5.x series release since we were somewhat edge case in our usage.

@GrahamCampbell
Copy link
Contributor

The change has been reverted for PHPUnit 5.1 and was rescheduled for PHPUnit 6.

👍 for that, regardless of what laravel's doing.

@tmatsuo
Copy link

tmatsuo commented May 13, 2016

@sebastianbergmann
Do you still think it's not worthwhile to introduce a configuration?

I'm asking because currently PHPUnit 5.x has the order setUp -> @before (also setUpBeforeClass -> @beforeClass), and PHPUnit 6.x will change it, right? Then it's hard for us to build something reliable leveraging this feature that works on both. Are there any suggestions for that?

In particular, I'm trying to build an e2e test harness for a PaaS. It's very similar to the first comment from @beberlei but I'm actually trying to do the opposite. I want to run something before @beforeClass.

class MyTest extends PHPunit_Framework_TestCase
{
    use PaasDeploymentTestTrait; // It deploys an app in @beforeClass

    public static function setUpBeforeClass()
    {
           // I want to run this before the PaasDeploymentTestTrait's @beforeClass
           self::dumpConfigFromEnv(); 
    }
}

@sebastianbergmann sebastianbergmann added type/backward-compatibility Something will be/is intentionally broken and removed type/bug Something is broken labels Jun 4, 2016
@sebastianbergmann sebastianbergmann changed the title Consider switching @before and setUp() order Switch the order in which @before methods and setUp() are executed Jun 5, 2016
@sebastianbergmann sebastianbergmann changed the title Switch the order in which @before methods and setUp() are executed Switch the order in which @before methods and setUp() are executed Jun 5, 2016
sebastianbergmann added a commit that referenced this issue Aug 19, 2016
@mfn
Copy link

mfn commented Jun 4, 2017

Came here because I upgraded phpunit6 in a Laravel 5.4 application and my own trait extensions broke because of this change.

Before this change, I was able to do this:

SomeTrait.php

trait SomeTrait {
    /**
     * @before
    */
    protected function someInitializationRequiresSetupBeingCalled() {
        $this->somethingInitializedInSetup->foo();
    }

TestCase.php

abstract class TestCase extends  BaseTestCase {
    protected function setUp() {
        $this->somethingInitializedInSetup = initialize_it();
    }
}

SomeTest.php

class SomeTest {
    use SomeTrait;
    public function testSomething() {
        // some test
    }
}

Previously, with these examples, it was possible to just by including SomeTrait you could run additional code after setUp.

The way Laravel augmented this is they have setUp which checks if a certain trait was loaded and calls its (trait specific) initializer method.

Of course it's possible to do it that way, but before I change my code I would rather find out if there is a better way?

Can I, by using @before or something similar, run code now between the setUp and the actual test method with phpunit6 so I don't have to be specific about it via my setUp?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/backward-compatibility Something will be/is intentionally broken
Projects
None yet
Development

Successfully merging a pull request may close this issue.