# Test Suites

A test suite is a way to organize `TestCase` implementations together. A client interacting with the framework could then allow you to filter which tests get run by only running a specific test suite. However, a `TestSuite` can be much more than that as it is a first-class citizen and a fundamental aspect of how the framework operates.

In our example we're going to assume the code under test has a heavy initialization cost or is not something that works well with the existing Amp PHPUnit wrapper. We're going to implement a `TestSuite` that will instantiate **just one** of these `Heavy` objects for *all* of the tests associated to it. Additionally, the `TestSuite` will perform some operation before each test to ensure that the object is properly setup.

### Defining a TestSuite

The first thing is define the `TestSuite`.&#x20;

```php
<?php

use Cspray\Labrador\AsyncUnit\TestSuite;
use Cspray\Labrador\AsyncUnit\Attribute\BeforeAll;
use Cspray\Labrador\AsyncUnit\Attribute\BeforeEachTest;
use Generator;

class HeavyTestSuite extends TestSuite {

   #[BeforeAll]
   public function createHeavy() : Generator {
      $heavy = yield HeavyFactory::create();
      $this->set('heavy', $heavy);
   }   
   
   #[BeforeEachTest]
   public function runHeavy() : Generator {
      yield $this-get('heavy')->doSomethingHeavy();
   }
   
   #[AfterEachTest]
   public function freeHeavy() : Generator {
      yield $this->get('heavy')->free();
      $this->set('heavy', null);
   }

}
```

For those familiar with unit testing it should be clear what's happening in the code above. Let's take a step-by-step look at what's happening.

#### The BeforeAll Hook

```php
#[BeforeAl]
public function createHeavy() : void {
    $this->set('heavy', new Heavy());
}
```

The "[TestCase Hooks](https://docs.labrador-kennel.io/async-unit/tutorials/testcase-hooks)" article introduces the `#[BeforeAll]` Attribute. In that scenario the Attribute was used on a `TestCase` where this time it is declared on a `TestSuite`. There's a couple things to note about this hook though; it isn't a static method and it is run before *all* associated `TestCase` implementations are executed. A `TestSuite` lives higher up the test hierarchy and therefore so does its hooks!&#x20;

This hook also set some arbitrary data; in this case an instance of `Heavy`, that we make use of later on. This use case, instantiating objects with a heavy initialization cost for later use, is the primary reason for the existence of the test suite functionality! A single `TestSuite` object is created for all associated tests so that this type of state can be shared.

#### The BeforeEachTest Hook

```php
#[BeforeEachTest]
public function runHeavy() : Generator {
   yield $this-get('heavy')->run();
}
```

This hook is not one covered in "[TestCase Hooks](https://docs.labrador-kennel.io/async-unit/tutorials/testcase-hooks)"; because it is only available to the `TestSuite` type. Since a `TestSuite` lives higher up if we were to run `BeforeEach` it would only execute one time for each `TestCase` but this should run for each *test*. This hook allows doing just that. We simply get our previously created object and perform some operation on it.

#### The AfterEachTest Hook

```php
#[AfterEachTest]
   public function freeHeavy() : Generator {
      yield $this->get('heavy')->free();
      $this->set('heavy', null);
   }
```

This hook is the parallel to `BeforeEachTest` and is run after each test is finished processing. This implementation simply frees up whatever resources were being used by `Heavy` and ensures that at some point the object gets garbage collected.

### Declaring a TestSuite

Now that a `TestSuite` has been implemented the appropriate `TestCase` need to be associated to it. There's currently 2 ways in which to do this. Making a `TestSuite` the default or explicitly declaring on each `TestCase` what suite it belongs to.

#### Making a TestSuite the default

This will cause every single `TestCase` that does not explicitly declare a `TestSuite` to be associated to the class being annotated. There can only be 1 default suite at a time. Simply use the `DefaultTestSuite` Attribute.

```php
use Cspray\Labrador\AsyncUnit\Attribute\DefaultTestSuite;

#[DefaultTestSuite]
class HeavyTestSuite extends TestSuite {
```

#### Declare on each TestCase

It is also possible to explicitly state which `TestSuite` a `TestCase` belongs to. In this case put the annotation on the `TestCase` and use the `AttachToTestSuite` Attribute. For example...

```php
use Cspray\Labrador\AsyncUnit\Attribute\AttachToTestSuite;
use Cspray\Labrador\AsyncUnit\TestCase;

#[AttachToTestSuite(HeavyTestSuite::class)]
class SomeHeavyTestCase extends TestCase {}
```

{% hint style="warning" %}
There's currently a known limitation on the `AttachToTestSuite` Attribute that requires you to define the `TestSuite` using class constant notation. Defining your `TestSuite` with a literal string is not currently supported. Future improvements to the static analyzer will remediate this problem.
{% endhint %}

### There's always a TestSuite

It is important to realize that even if there is no explicit `TestSuite` there is still an instance created and all of the `TestCase` and tests are associated to it. If the parser can't find an explicit `TestSuite` AsyncUnit will create an instance of `ImplicitTestSuite` to use instead. This implementation performs no operations and has no hooks defined.

### Understand the Hook lifecycle

Before you implement your own `TestSuite` with custom hooks  ensure the ramifications are well understood. With great power comes great responsiblity and implementing an explicit `TestSuite`, while sometimes necessary, also brings an additional level of complexity. Make sure you've read over the Hooks reference!

{% content-ref url="../reference/hooks" %}
[hooks](https://docs.labrador-kennel.io/async-unit/reference/hooks)
{% endcontent-ref %}
