# Writing Custom Assertions

### Understanding the Assertion interface

Before you can write your own `Assertion` first you should make sure you understand its API and responsibility. It is responsible for determining whether or not some condition is true or false and to give details on the values that are used to determine whether that condition is true or false.

```php
<?php

namespace Cspray\Labrador\AsyncUnit;

interface Assertion {

    public function assert() : AssertionResult;

}
```

The `AssertionResult` returned holds the information about whether the condition we were checking passed and the information about the values used. Since the `assert()` method itself doesn't actually have any arguments everything that you might need for the check should be passed in to the constructor of the Assertion.

Ultimately, that's it! There's some other setup that will be required to actually get your Assertion to be usable within the framework. We'll get to that momentarily but first let's finish step 1!

{% hint style="info" %}
This guide focuses on creating a synchronous `Assertion`. Creating an `AsyncAssertion` follows the same principles except that its `assert()` method returns a `Promise` that should resolve to the `AssertionResult`.
{% endhint %}

### Assertion rules

Before we get started with the actual Assertion implementation let's go over a few rules we expect Assertions to follow.

* Your `Assertion` MUST NOT throw an exception unless a truly unexpected circumstance has occurred. If an exception could be expected to be thrown during your condition checking you should catch it, mark your `AssertionResult` as failed, and provide the appropriate information about that thrown exception.
* Your `AssertionResult` MUST include summary information about both the normal case and the *not* case. An `Assertion` SHOULD never make assumptions about whether it is being called in the normal case or the *not* case. If you haven't done so you can read up about the *not* case in "[Getting Started](https://docs.labrador-kennel.io/async-unit/tutorials/getting-started#asserting-not-conditions)".

### Step 1 - Writing your Assertion

For our example we'll create an Assertion that will check if an object with a given type returns the correct value from an invoked method. Let's assume that the objects under test will implement the below interfaces:

```php
<?php

interface WidgetService {

    public function getWidget() : Widget;

}

interface Widget {
    
    public function getName() : string;
    
}
```

Ultimately, what we want our Assertion to do is verify that a `Widget` produced by a given `WidgetService` has the correct name. Now, let's actually write our custom implementation!

{% hint style="info" %}
In a more thorough, "real" implementation you would ensure that these Assertions are properly unit tested!
{% endhint %}

```php
<?php

use Cspray\Labrador\AsyncUnit\Assertion;

class WidgetServiceWidgetNameAssertion implements Assertion {

    public function __construct(
        private string $expected, 
        private WidgetService $actual
    ) {}

    public function assert() : AssertionResult {
         $actual = $this->actual->getWidget()->getName();
         $passes = $this->expected === $actual;
    }
    
}
```

### Step 2 - Write your summary AssertionMessage

The next step is to write an `AssertionMessage` used to provide a summary of the assertion. The summary shouldn't go into heavy details about any potential problems with the assertion, just a brief description of how the assertion failed. These messages are ultimately used as a fragment in a greater message. For example, if the assertion failed you might expect to see output like the following...

> **Failed** *comparing that 2 values are equal to one another*.

In the above message only the emphasized text is provided by you. The bold **Failed** is part of the message provided by other parts of the framework. Why do we do it this way? In future functionality we expect to be able to allow for doing things like creating a summary of *all* assertions made or allowing you to skip *individual assertions* in a test. In those cases the messages output by the system might look like...

> **Succeeded** *comparing that 2 values are equal to one another.*

> **Skipped** *comparing that 2 values are equal to one another.*

Your Assertion wouldn't know for any given invocation in what context it is being invoked; "Failed", "Succeeded", and "Skipped" are all contextual pieces of information that the Assertion doesn't, and shouldn't, have access to. So, when writing summary messages make sure that you construct them in such a way that it works with all 3 of the scenarios described above.

If possible we recommend you make use of one of the existing `AssertionMessage` implementations. However, in our example none of the existing implementations really does what we need it to do. We will create our own, in this case an anonymous class that implements the `AssertionMessage` interface.

```php
<?php

use Cspray\Labrador\AsyncUnit\Assertion;

class WidgetServiceWidgetNameAssertion implements Assertion {

    public function __construct(
        private string $expected, 
        private WidgetService $actual
    ) {}

    public function assert() : AssertionResult {
         $actual = $this->actual->getWidget()->getName();
         $passes = $this->expected === $actual;
    }
    
    private function getSummary() : AssertionMessage {
        return new class($this->actual) implements AssertionMessage {
             public function __construct(private WidgetService $actual) {}
             
             public function toString() : string {
                return sprintf("asserting %s creates expected Widget", $this->actual::class);                 
             }
             
             public function toNotString() : string {
                 return sprintf("asserting %s does not create expected Widget", $this->actual::class);
             }              
        }
    }
    
}
```

For now, that's it! We'll make use of this method a little later on.

### Step 3 - Write your detailed messages

Obviously if something went wrong the summary messages above wouldn't provide nearly enough information to diagnose what is happening. The responsibility for creating what could potentially be highly complex messages is handled by a detailed `AssertionMessage`. Just like summary messages this implementation requires providing both the normal and *not* use cases.

{% hint style="info" %}
The same r[ules above about summary messages](#step-2-write-your-summary-messages) interacting with the rest of the framework applies to detailed messages as well!
{% endhint %}

```php
<?php

use Cspray\Labrador\AsyncUnit\Assertion;

class WidgetServiceWidgetNameAssertion implements Assertion {

    public function __construct(
        private string $expected, 
        private WidgetService $actual
    ) {}

    public function assert() : AssertionResult {
         $actual = $this->actual->getWidget()->getName();
         $passes = $this->expected === $actual;
    }
    
    private function getSummary() : AssertionMessage {
        return new class($this->actual) implements AssertionMessage {
             public function __construct(private WidgetService $actual) {}
             
             public function toString() : string {
                return sprintf("asserting %s creates expected Widget", $this->actual::class);                 
             }
             
             public function toNotString() : string {
                 return sprintf("asserting %s does not create expected Widget", $this->actual::class);
             }              
        }
    }
    
    private function getDetails() : AssertionMessage {
        return new class($this->expected, $this->actual) implements AssertionMessage {
            public function __construct(
                private string $expected, 
                private WidgetService $actual
            ) {}
            
            public function toString() : string {
                return sprintf(
                    "asserting %s creates a Widget with name \"%s\"",
                    $this->actual::class,
                    $this->expected
                );
            }
            
            public function toNotString() : string {
                return sprintf(
                    "asserting %s creates a Widget with name different than \"%s\"",
                    $this->actual::class,
                    $this->expected
                );
            }
        }
    }
    
}
```

And that's it for messages! In different assertions it could be easy to see how including type information or diffs could cause the detailed message to become very complex. Many assertions though will follow this pattern where the detailed messages simply provides additional context about the values being asserted.

### Step 4 - Return your AssertionResult

Next is to get the information about your assertion returned with an `AssertionResult`. To create concrete implementations of this interface you can use the `AssertionResultFactory` or roll your own! For our example we'll make use of the existing factory. Continuing to build upon what we've worked on so far let's finish off our custom `Assertion`!

```php
<?php

use Cspray\Labrador\AsyncUnit\Assertion;
use Cspray\Labrador\AsyncUnit\Assertion\AssertionResultFactory;

class WidgetServiceWidgetNameAssertion implements Assertion {

    public function __construct(
        private string $expected, 
        private WidgetService $actual
    ) {}

    public function assert() : AssertionResult {
        $actual = $this->actual->getWidget()->getName();
        $passes = $this->expected === $actual;
        $factoryMethod = $passes ? 'validAssertion' : 'invalidAssertion';
        return AssertionResultFactory::$factoryMethod(
            $this->getSummary(),
            $this->getDetail()
        );
    }
    
    private function getSummary() : AssertionMessage {
        return new class($this->actual) implements AssertionMessage {
             public function __construct(private WidgetService $actual) {}
             
             public function toString() : string {
                return sprintf("asserting %s creates expected Widget", $this->actual::class);                 
             }
             
             public function toNotString() : string {
                 return sprintf("asserting %s does not create expected Widget", $this->actual::class);
             }              
        }
    }
    
    private function getDetails() : AssertionMessage {
        return new class($this->expected, $this->actual) implements AssertionMessage {
            public function __construct(
                private string $expected, 
                private WidgetService $actual
            ) {}
            
            public function toString() : string {
                return sprintf(
                    "asserting %s creates a Widget with name \"%s\"",
                    $this->actual::class,
                    $this->expected
                );
            }
            
            public function toNotString() : string {
                return sprintf(
                    "asserting %s creates a Widget with name different than \"%s\"",
                    $this->actual::class,
                    $this->expected
                );
            }
        }
    }
    
}
```

And we're done with writing our Assertion! Now we need to make sure we let the framework know about it so it can be used within your tests.

### Step 5 - Add your Assertion to the framework

It is very important that all assertions, even those you custom implement, happen with the Assertion API provided by the framework; namely, you should expect to invoke something similar to `assert()->widgetServiceCreatesNamedWidget('widgetName', $widgetService)` in your test cases to actually invoke your Assertion. We'll accomplish this by implementing the `CustomAssertionPlugin` provided by the framework.

```php
<?php

use Cspray\Labrador\AsyncUnit\CustomAssertionPlugin;
use Cspray\Labrador\AsyncUnit\Context\CustomAssertionContext;

class WidgetServiceWidgetNameAssertionPlugin implements CustomAssertionPlugin {

    public function registerCustomAssertions(CustomAssertionContext $context) : Promise {
        $context->registerAssertion('widgetServiceCreatesNamedWidget', function(string $expected, WidgetService $actual) {
            return new WidgetServiceWidgetNameAssertion($expected, $actual);
        });
    }

}
```

And that's it! As long as this implementation is within the directories scanned by the framework in your TestCases you will be able to invoke your custom assertion!  Happy Asserting!

{% hint style="info" %}
If you'd like to learn more about how the Plugin system works within the framework we recommend reading over [the Labrador Core documentation about Plugins](https://www.labrador-kennel.io/docs/core/tutorials/plugins-overview/).
{% endhint %}
