Skip to content

[8.x] Adds first-level test traits setUp/tearDown (opt-in) #39883

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

Closed
wants to merge 2 commits into from
Closed

[8.x] Adds first-level test traits setUp/tearDown (opt-in) #39883

wants to merge 2 commits into from

Conversation

DarkGhostHunter
Copy link
Contributor

What?

Allows the developer to include and reuse traits that automatically set-up and tear-down in each test, when declared at first-level.

Given a reusable test WithExamplePost:

namespace Tests\Feature\Concerns;

use App\Models\Post;

trait WithExamplePost
{    
    protected $post;

    protected function setUpWithExamplePost()
    {
        Post::factory()->createOne();
    }
    
    protected function tearDownWithExamplePost()
    {
        $this->post = null;
    }
}

The test class implementing InitializeTraits, will automatically call setUpWithExamplePost after the application is created, and tearDownWithExamplePost before is destroyed.

<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Contracts\Testing\InitializeTraits;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase implements InitializeTraits
{
    use RefreshDatabase;
    use Concerns\WithSubscribedUser;

    public function test_post_is_visible()
    {
        $this->get('post/' . $this->post->id)->assertOk();
    }
}

Since this is an additive opt-in feature, no test will break unless the interface is added on tests classes.

Why?

There are three keys for this implementation:

  • Reusable traits out of the box, no need to implement the feature by the developer.
  • Opt-in
  • Runs after the application is created, and before it's destroyed.
  • Only works for first-level traits. Nested traits are not affected (like in PHPUnit), so initialization is controllable.

BC?

Nope, opt-in additive.

Notes

I decided to use an interface-check in the TestCase rather than adding a trait because:

  • Avoids getting the trait into the initializers array (or even alone by itself, making no sense)
  • Avoids polluting a test properties or methods if it were to check them.

Didn't I see this before?

Kinda

@inxilpro
Copy link
Contributor

inxilpro commented Dec 6, 2021

I think this would be a nice addition. I did want to mention that you can accomplish this right now with the following:

trait MyTrait
{
  /** @before */
  protected function setUpMyTrait()
  {
    $this->afterApplicationCreated(function() {
      // Perform setup (must happen in afterApplicationCreated because
      // everything with the @before annotation runs BEFORE setUp() is called
    });
  }
  
  /** @after */
  protected function tearDownMyTrait()
  {
    // Perform tear down (runs after tearDown())
  }
}

But I think the confusion about how the @before and @after hooks are ordered makes it a pain—it'd be nice for it to just be handled by the framework.

Edit: Any @after hooks run after tearDown() which means they don't have access to the Laravel application

@DarkGhostHunter
Copy link
Contributor Author

I also do it in some of my testing traits but feels less straightforward using annotations.

@driesvints
Copy link
Member

Ah I didn't immediately remember that you could do this using the annotations. In that case I wouldn't add this to the core framework. There's also a slight chance of breakages with this PR if people have already defined such methods.

@DarkGhostHunter
Copy link
Contributor Author

If there is a minimal collision risk, I can push this to 9.x then, and remove the interface.

@inxilpro
Copy link
Contributor

inxilpro commented Dec 6, 2021

Ah I didn't immediately remember that you could do this using the annotations. In that case I wouldn't add this to the core framework. There's also a slight chance of breakages with this PR if people have already defined such methods.

I still feel like the @before and @after annotation behavior is confusing due to the way they're applied. Adding framework-level support for dynamically applying traits in a predictable way would be great (it's something I proposed a few years ago, as well)

I suppose another option would be to add a new @afterApplicationCreated and @beforeApplicationDestroyed annotation that's Laravel-specific…

@inxilpro
Copy link
Contributor

inxilpro commented Dec 6, 2021

I actually just dug around some more and I was wrong, the @after annotation will cause the method to be called after the test class' tearDown() method, which means you won't have access to any of the mocked Laravel app. You're better off adding afterApplicationCreated and beforeApplicationDestroyed closures inside a @before hook if you need the Laravel app to still be set up.

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If possible, please consider releasing your code as a package so that the community can still take advantage of your contributions!

If you feel absolutely certain that this code corrects a bug in the framework, please "@" mention me in a follow-up comment with further explanation so that GitHub will send me a notification of your response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants