Skip to content
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

Provide assertion helpers through TestResponseMixin #1308

Merged
merged 5 commits into from
Apr 19, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Provide assertion helpers through TestResponseMixin
spawnia committed Apr 19, 2020
commit 418226547d869c4eb22f9b3eb327ca8f74e4d93c
2 changes: 0 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -9,13 +9,11 @@
/.gitattributes export-ignore
/.gitignore export-ignore
/.styleci.yml export-ignore
/_ide_helper.php export-ignore
/CHANGELOG.md export-ignore
/CONTRIBUTING.md export-ignore
/docker-compose.yml export-ignore
/Dockerfile export-ignore
/logo.png export-ignore
/logo.png export-ignore
/Makefile export-ignore
/netlify.toml export-ignore
/phpbench.json export-ignore
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ You can find and compare releases at the [GitHub release page](https://github.co

- Add flag `--json` to `print-schema` to output JSON instead of GraphQL SDL https://github.com/nuwave/lighthouse/pull/1268
- Add TTL option for subscriptions storage https://github.com/nuwave/lighthouse/pull/1284
- Provide assertion helpers through `TestResponseMixin`

### Fixed

82 changes: 76 additions & 6 deletions _ide_helper.php
Original file line number Diff line number Diff line change
@@ -6,10 +6,43 @@ class TestResponse
/**
* Asserts that the response contains an error from a given category.
*
* @param string $category
* @param string $category The name of the expected error category.
* @return $this
*/
public function assertErrorCategory(string $category): self
public function assertGraphQLErrorCategory(string $category): self
{
return $this;
}

/**
* Assert that the returned result contains exactly the given validation keys.
*
* @param array $keys The validation keys the result should have.
* @return $this
*/
public function assertGraphQLValidationKeys(array $keys): self
{
return $this;
}

/**
* Assert that a given validation error is present in the response.
*
* @param string $key The validation key that should be present.
* @param string $message The expected validation message.
* @return $this
*/
public function assertGraphQLValidationError(string $key, string $message): self
{
return $this;
}

/**
* Assert that no validation errors are present in the response.
*
* @return $this
*/
public function assertGraphQLValidationPasses(): self
{
return $this;
}
@@ -20,7 +53,8 @@ public function assertErrorCategory(string $category): self
* @param string|null $key
* @return mixed
*/
public function jsonGet(string $key = null) {
public function jsonGet(string $key = null)
{
return;
}
}
@@ -32,10 +66,43 @@ class TestResponse
/**
* Asserts that the response contains an error from a given category.
*
* @param string $category
* @param string $category The name of the expected error category.
* @return $this
*/
public function assertErrorCategory(string $category): self
public function assertGraphQLErrorCategory(string $category): self
{
return $this;
}

/**
* Assert that the returned result contains exactly the given validation keys.
*
* @param array $keys The validation keys the result should have.
* @return $this
*/
public function assertGraphQLValidationKeys(array $keys): self
{
return $this;
}

/**
* Assert that a given validation error is present in the response.
*
* @param string $key The validation key that should be present.
* @param string $message The expected validation message.
* @return $this
*/
public function assertGraphQLValidationError(string $key, string $message): self
{
return $this;
}

/**
* Assert that no validation errors are present in the response.
*
* @return $this
*/
public function assertGraphQLValidationPasses(): self
{
return $this;
}
@@ -46,7 +113,8 @@ public function assertErrorCategory(string $category): self
* @param string|null $key
* @return mixed
*/
public function jsonGet(string $key = null) {
public function jsonGet(string $key = null)
{
return;
}
}
@@ -56,6 +124,8 @@ public function jsonGet(string $key = null) {
class ResolveInfo
{
/**
* We monkey patch this onto here to pass it down the resolver chain.
*
* @var \Nuwave\Lighthouse\Execution\Arguments\ArgumentSet
*/
public $argumentSet;
22 changes: 22 additions & 0 deletions docs/master/testing/phpunit.md
Original file line number Diff line number Diff line change
@@ -124,6 +124,28 @@ public function testOrdersUsersByName(): void
}
```

### TestResponse Assertion Mixins

Lighthouse conveniently provides additional assertions as mixins to the `TestResponse` class.
Make sure to publish the latest [IDE-helper file](/_ide_helper.php) to get proper autocompletion:

```bash
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=ide-helper
```

The provided assertions are prefixed with `assertGraphQL` and offer useful shortcuts to common testing
tasks. For example, you might want to ensure that validation works properly:

```php
$this
->graphQL(/** @lang GraphQL */ '
mutation {
createUser(email: "invalid email")
}
')
->assertGraphQLValidationKeys(['email']);
```

## Simulating File Uploads

Lighthouse allows you to [upload files](../digging-deeper/file-uploads.md) through GraphQL.
5 changes: 4 additions & 1 deletion src/LighthouseServiceProvider.php
Original file line number Diff line number Diff line change
@@ -68,6 +68,10 @@ public function boot(ValidationFactory $validationFactory, ConfigRepository $con
__DIR__.'/../assets/default-schema.graphql' => $configRepository->get('lighthouse.schema.register'),
], 'schema');

$this->publishes([
__DIR__.'/../_ide_helper.php' => $this->app->make('path.base').'/_lighthouse_ide_helper.php',
], 'ide-helper');

$this->loadRoutesFrom(__DIR__.'/Support/Http/routes.php');

$validationFactory->resolver(
@@ -112,7 +116,6 @@ public function register(): void
$this->app->singleton(CanStreamResponse::class, ResponseStream::class);

$this->app->bind(CreatesResponse::class, SingleResponse::class);

$this->app->bind(GlobalIdContract::class, GlobalId::class);

$this->app->singleton(GraphQLRequest::class, function (Container $app): GraphQLRequest {
2 changes: 1 addition & 1 deletion src/Testing/MakesGraphQLRequests.php
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
* Useful helpers for PHPUnit testing.
* Testing helpers for making requests to the GraphQL endpoint.
*
* @mixin \Illuminate\Foundation\Testing\Concerns\MakesHttpRequests
*/
86 changes: 86 additions & 0 deletions src/Testing/TestResponseMixin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Nuwave\Lighthouse\Testing;

use Closure;
use PHPUnit\Framework\Assert;

/**
* @mixin \Illuminate\Foundation\Testing\TestResponse
*/
class TestResponseMixin
{
public function assertGraphQLValidationError(): Closure
{
return function (string $key, ?string $message) {
$this->assertJson([
'errors' => [
[
'extensions' => [
'validation' => [
$key => [
$message,
],
],
],
],
],
]);

return $this;
};
}

public function assertGraphQLValidationKeys(): Closure
{
return function (array $keys) {
$validation = TestResponseUtils::extractValidationErrors($this);

Assert::assertSame(
$keys,
array_keys($validation['extensions']['validation']),
'Expected the query to return validation errors for specific fields.'
);

return $this;
};
}

public function assertGraphQLValidationPasses(): Closure
{
return function () {
$validation = TestResponseUtils::extractValidationErrors($this);

Assert::assertNull(
$validation,
'Expected the query to have no validation errors.'
);

return $this;
};
}

public function assertGraphQLErrorCategory(): Closure
{
return function (string $category) {
$this->assertJson([
'errors' => [
[
'extensions' => [
'category' => $category,
],
],
],
]);

return $this;
};
}

public function jsonGet(): Closure
{
return function (string $key = null) {
return data_get($this->decodeResponseJson(), $key);
};
}
}
29 changes: 29 additions & 0 deletions src/Testing/TestResponseUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Nuwave\Lighthouse\Testing;

use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Exceptions\ValidationException;

/**
* Because we can not have non-mixin methods in mixin classes.
*
* @see \Nuwave\Lighthouse\Testing\TestResponseMixin
*/
class TestResponseUtils
{
/**
* @param \Illuminate\Foundation\Testing\TestResponse|\Illuminate\Testing\TestResponse $response
*/
public static function extractValidationErrors($response): ?array
{
$errors = $response->json('errors') ?? [];

return Arr::first(
$errors,
function (array $error): bool {
return Arr::get($error, 'extensions.category') === ValidationException::CATEGORY;
}
);
}
}
6 changes: 6 additions & 0 deletions src/Testing/TestingServiceProvider.php
Original file line number Diff line number Diff line change
@@ -24,5 +24,11 @@ static function (): string {
public function register(): void
{
$this->app->singleton(MockDirective::class);

if (class_exists('Illuminate\Testing\TestResponse')) {
\Illuminate\Testing\TestResponse::mixin(new TestResponseMixin());
} elseif (class_exists('Illuminate\Foundation\Testing\TestResponse')) {
\Illuminate\Foundation\Testing\TestResponse::mixin(new TestResponseMixin());
}
}
}
2 changes: 1 addition & 1 deletion tests/Integration/Schema/Directives/CanDirectiveDBTest.php
Original file line number Diff line number Diff line change
@@ -213,7 +213,7 @@ public function testThrowsIfNotAuthorized(): void
title
}
}
")->assertErrorCategory(AuthorizationException::CATEGORY);
")->assertGraphQLErrorCategory(AuthorizationException::CATEGORY);
}

public function testCanHandleMultipleModels(): void
Original file line number Diff line number Diff line change
@@ -276,7 +276,7 @@ public function testHandlesPaginationWithCountZero(): void
'tasks' => null,
],
],
])->assertErrorCategory(Error::CATEGORY_GRAPHQL);
])->assertGraphQLErrorCategory(Error::CATEGORY_GRAPHQL);
}

public function testRelayTypeIsLimitedByMaxCountFromDirective(): void
Original file line number Diff line number Diff line change
@@ -354,7 +354,7 @@ public function testHandlesPaginationWithCountZero(): void
'images' => null,
],
],
])->assertErrorCategory(Error::CATEGORY_GRAPHQL);
])->assertGraphQLErrorCategory(Error::CATEGORY_GRAPHQL);
}

public function testCanQueryMorphManyPaginatorWithADefaultCount(): void
14 changes: 8 additions & 6 deletions tests/Integration/Subscriptions/SubscriptionTest.php
Original file line number Diff line number Diff line change
@@ -110,13 +110,15 @@ public function testCanBroadcastSubscriptions(): void

public function testThrowsWithMissingOperationName(): void
{
$this->graphQL('
subscription {
onPostCreated {
body
$this
->graphQL(/** @lang GraphQL */ '
subscription {
onPostCreated {
body
}
}
}
')->assertErrorCategory('subscription')
')
->assertGraphQLErrorCategory('subscription')
->assertJson([
'data' => [
'onPostCreated' => null,
6 changes: 0 additions & 6 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -127,12 +127,6 @@ protected function getEnvironmentSetUp($app)
);

$config->set('app.debug', true);

if (class_exists('Illuminate\Testing\TestResponse')) {
\Illuminate\Testing\TestResponse::mixin(new TestResponseMixin());
} elseif (class_exists('Illuminate\Foundation\Testing\TestResponse')) {
\Illuminate\Foundation\Testing\TestResponse::mixin(new TestResponseMixin());
}
}

/**
35 changes: 0 additions & 35 deletions tests/TestResponseMixin.php

This file was deleted.

8 changes: 4 additions & 4 deletions tests/Unit/Execution/Arguments/TypedArgsTest.php
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ class TypedArgsTest extends TestCase
{
public function testSimpleField(): void
{
$this->schema = '
$this->schema = /** @lang GraphQL */ '
type Query {
foo(bar: Int): Int
}
@@ -32,7 +32,7 @@ public function testSimpleField(): void

public function testNullableList(): void
{
$this->schema = '
$this->schema = /** @lang GraphQL */ '
type Query {
foo(bar: [Int!]): Int
}
@@ -51,11 +51,11 @@ public function testNullableList(): void

public function testNullableInputObject(): void
{
$this->schema = '
$this->schema = /** @lang GraphQL */ '
type Query {
foo(bar: Bar): Int
}
input Bar {
baz: ID
}
26 changes: 13 additions & 13 deletions tests/Unit/Schema/AST/ASTHelperTest.php
Original file line number Diff line number Diff line change
@@ -11,13 +11,13 @@ class ASTHelperTest extends TestCase
{
public function testThrowsWhenMergingUniqueNodeListWithCollision(): void
{
$objectType1 = PartialParser::objectTypeDefinition('
$objectType1 = PartialParser::objectTypeDefinition(/** @lang GraphQL */ '
type User {
email: String
}
');

$objectType2 = PartialParser::objectTypeDefinition('
$objectType2 = PartialParser::objectTypeDefinition(/** @lang GraphQL */ '
type User {
email(bar: String): Int
}
@@ -33,14 +33,14 @@ public function testThrowsWhenMergingUniqueNodeListWithCollision(): void

public function testMergesUniqueNodeListsWithOverwrite(): void
{
$objectType1 = PartialParser::objectTypeDefinition('
$objectType1 = PartialParser::objectTypeDefinition(/** @lang GraphQL */ '
type User {
first_name: String
email: String
}
');

$objectType2 = PartialParser::objectTypeDefinition('
$objectType2 = PartialParser::objectTypeDefinition(/** @lang GraphQL */ '
type User {
first_name: String @foo
last_name: String
@@ -62,7 +62,7 @@ public function testMergesUniqueNodeListsWithOverwrite(): void

public function testCanExtractStringArguments(): void
{
$directive = PartialParser::directive('@foo(bar: "baz")');
$directive = PartialParser::directive(/** @lang GraphQL */ '@foo(bar: "baz")');
$this->assertSame(
'baz',
ASTHelper::directiveArgValue($directive, 'bar')
@@ -71,15 +71,15 @@ public function testCanExtractStringArguments(): void

public function testCanExtractBooleanArguments(): void
{
$directive = PartialParser::directive('@foo(bar: true)');
$directive = PartialParser::directive(/** @lang GraphQL */ '@foo(bar: true)');
$this->assertTrue(
ASTHelper::directiveArgValue($directive, 'bar')
);
}

public function testCanExtractArrayArguments(): void
{
$directive = PartialParser::directive('@foo(bar: ["one", "two"])');
$directive = PartialParser::directive(/** @lang GraphQL */ '@foo(bar: ["one", "two"])');
$this->assertSame(
['one', 'two'],
ASTHelper::directiveArgValue($directive, 'bar')
@@ -88,7 +88,7 @@ public function testCanExtractArrayArguments(): void

public function testCanExtractObjectArguments(): void
{
$directive = PartialParser::directive('@foo(bar: { baz: "foobar" })');
$directive = PartialParser::directive(/** @lang GraphQL */ '@foo(bar: { baz: "foobar" })');
$this->assertSame(
['baz' => 'foobar'],
ASTHelper::directiveArgValue($directive, 'bar')
@@ -97,18 +97,18 @@ public function testCanExtractObjectArguments(): void

public function testReturnsNullForNonExistingArgumentOnDirective(): void
{
$directive = PartialParser::directive('@foo');
$directive = PartialParser::directive(/** @lang GraphQL */ '@foo');
$this->assertNull(
ASTHelper::directiveArgValue($directive, 'bar')
);
}

public function testChecksWhetherTypeImplementsInterface(): void
{
$type = PartialParser::objectTypeDefinition('
type Foo implements Bar {
baz: String
}
$type = PartialParser::objectTypeDefinition(/** @lang GraphQL */ '
type Foo implements Bar {
baz: String
}
');
$this->assertTrue(ASTHelper::typeImplementsInterface($type, 'Bar'));
$this->assertFalse(ASTHelper::typeImplementsInterface($type, 'FakeInterface'));
4 changes: 2 additions & 2 deletions tests/Unit/Schema/Directives/CanDirectiveTest.php
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ public function testThrowsIfNotAuthorized(): void
name
}
}
')->assertErrorCategory(AuthorizationException::CATEGORY);
')->assertGraphQLErrorCategory(AuthorizationException::CATEGORY);
}

public function testPassesAuthIfAuthorized(): void
@@ -157,7 +157,7 @@ public function testProcessesTheArgsArgument(): void
name
}
}
')->assertErrorCategory(AuthorizationException::CATEGORY);
')->assertGraphQLErrorCategory(AuthorizationException::CATEGORY);
}

public function testInjectArgsPassesClientArgumentToPolicy(): void