# Testing Lighthouse extensions

When you extend Lighthouse with custom functionality, it is a great idea to test your extensions in isolation from the rest of your application.

# Use a test schema

When you enhance functionality related to the schema definition, such as adding a custom directive, you need a test schema where you can use it. Add the UsesTestSchema trait to your base test class, call setUpTestSchema() and define your test schema:

use Nuwave\Lighthouse\Testing\UsesTestSchema;

class MyCustomDirectiveTest extends TestCase
{
    use UsesTestSchema;

    protected function setUp(): void
    {
        parent::setUp();

        $this->setUpTestSchema();
    }

    public function testMyCustomOnArgument(): void
    {
        $this->schema = /** @lang GraphQL */ '
        type Query {
            foo(bar: String @myCustom): Int
        }
        ';

        // ...
    }
}

UsesTestSchema does not work with RefreshesSchemaCache, choose one.

# Mock resolvers

When testing custom functionality through a dummy schema, you still need to have a way to resolve fields. Lighthouse provides a simple way to mock resolvers in a dummy schema.

Add the MocksResolvers trait to your test class:

namespace Tests;

use Nuwave\Lighthouse\Testing\MocksResolvers;

class ReverseDirectiveTest extends TestCase
{
    use MocksResolvers;
}

In this example, we will be testing this fictional custom directive:

"""
Reverses a string, e.g. 'foo' => 'oof'.
"""
directive @reverse on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION

The simplest way to mock a resolver is to have it return static data:

public function testReverseField(): void
{
    $this->mockResolver('foo');

    $this->schema = /** @lang GraphQL */ '
    type Query {
        foo: String @reverse @mock
    }
    ';

    $this->graphQL(/** @lang GraphQL */ '
    {
        foo
    }
    ')->assertExactJson([
        'data' => [
            'foo' => 'oof',
        ],
    ]);
}

Since we get back an instance of PHPUnit's InvocationMocker, we can also assert that our resolver is called with certain values. Note that we are not passing an explicit resolver function here. The default resolver will simply return null.

public function testReverseInput(): void
{
    $this->mockResolver()
        ->with(null, ['bar' => 'rab']);

    $this->schema = /** @lang GraphQL */ '
    type Query {
        foo(bar: String @reverse): String @mock
    }
    ';

    $this->graphQL(/** @lang GraphQL */ '
    {
        foo(bar: "bar")
    }
    ')->assertExactJson([
        'data' => [
            'foo' => null,
        ],
    ]);
}

If you have to handle the incoming resolver arguments dynamically, you can also pass a function that is called:

public function testReverseInput(): void
{
    $this->mockResolver(function($root, array $args): string {
        return $args['bar'];
    });

    $this->schema = /** @lang GraphQL */ '
    type Query {
        foo(bar: String @reverse): String @mock
    }
    ';

    $this->graphQL(/** @lang GraphQL */ '
    {
        foo(bar: "bar")
    }
    ')->assertExactJson([
        'data' => [
            'foo' => 'rab',
        ],
    ]);
}

You might have a need to add multiple resolvers to a single schema. For that case, specify a unique key for the mock resolver (it defaults to default):

public function testMultipleResolvers(): void
{
    $this->mockResolver(..., 'first');
    $this->mockResolver(..., 'second');

    $this->schema = /** @lang GraphQL */ '
    type Query {
        foo: Int @mock(key: "first")
        bar: ID @mock(key: "second")
    }
    ';
}

By default, the resolver from mockResolver expects to be called at least once. If you want to set a different expectation, you can use mockResolverExpects:

public function testAbortsBeforeResolver(): void
{
    $this->mockResolverExpects(
        $this->never()
    );

    $this->schema = /** @lang GraphQL */ '
    type Query {
        foo: Int @someValidationThatFails @mock
    }
    ';

    $this->graphQL(/** @lang GraphQL */ '
    {
        foo
    }
    ');
}