# 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](/async-unit/tutorials/testcase-hooks.md)" 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](/async-unit/tutorials/testcase-hooks.md)"; 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="/pages/-M\_B\_61ltidAE4UwowCa" %}
[Hooks](/async-unit/reference/hooks.md)
{% endcontent-ref %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.labrador-kennel.io/async-unit/tutorials/test-suites.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
