-
Notifications
You must be signed in to change notification settings - Fork 99
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
Writing integration tests against graphql query #252
Labels
documentation
Related to errors, omissions and improvements to docs
Comments
This is something we need to document (and make easier) |
oojacoboo
added
the
documentation
Related to errors, omissions and improvements to docs
label
Mar 29, 2021
We might consider providing an abstract phpunit test class or trait for this. We've created a way of executing operations for tests using the following PHPUnit test class: <?php
declare(strict_types = 1);
namespace Test\Integration;
use Acme\Widget\Request\Handler as RequestHandler;
use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface;
/**
* Base GraphQL integration test class
*
* @author Jacob Thomason <[email protected]>
*/
abstract class AbstractGraphQLTest extends TestCase
{
private RequestHandler $requestHandler;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->requestHandler = $this->getContainer()->get(RequestHandler::class);
}
/**
* Executes a GraphQL operation request
*
* Consider executing this through another process, or even sending it through HTTP. As
* it is now, we're doing a lot of fancy stuff with the EntityManager to make it work.
*
* It is working quite well though and stable, just that the entity manager has to have it's
* state carefully managed. Moving to another actual HTTP request would make it very difficult
* to get stack traces and introduce Nginx overhead. A separate PHP process might be the best
* solution, if we're able to get the stack-traces.
*
* @param mixed[] $variables
* @param UploadedFileInterface[] $uploadedFiles
*/
protected function operation(
string $operation,
string $operationType,
array $variables = [],
array $uploadedFiles = [],
array $map = [],
): ResponseInterface
{
// We must clear the entity manager since we're often creating entities/records that we then
// want to test with the GraphQL layer. Since these entities are cached, if it's not cleared,
// it will always find them using the cache. In many cases, especially when testing the
// multi-tenancy functionality, we're executing these requests as a different role and therefore
// the cache shouldn't be used and a fresh query should be done.
$this->getContainer()->get('entity_manager_registry')->getManager()->clear();
$contentType = 'application/json';
$parsedBody = [
'query' => $operation,
'variables' => $variables,
'operationName' => $this->getName(),
];
// With uploads we have to use the multipart content-type, but also the request body differs.
// @see https://github.com/jaydenseric/graphql-multipart-request-spec
// Also we have to json_encode these property values for the GraphQL upload lib we're using.
// The inconsistency here really sucks and causes a number of weird conditional logic.
if ($uploadedFiles) {
$contentType = 'multipart/form-data; boundary=----WebKitFormBoundarySl4GaqVa1r8GtAbn';
$parsedBody = [
'operations' => json_encode($parsedBody),
'map' => json_encode($map),
];
}
$request = (new ServerRequest([], [], '/graphql', 'POST'))
->withHeader('content-type', $contentType)
->withHeader('Authorization', 'Bearer ' . /*get your auth key/token*/)
->withParsedBody($parsedBody)
->withUploadedFiles($uploadedFiles);
return $this->requestHandler->handle($request);
}
/**
* Execute a GraphQL query
*
* @param mixed $params,...
*/
protected function query(string $query, ...$params): ResponseInterface
{
$query = sprintf('query %s {%s}', $this->getName(), sprintf($query, ...$params));
return $this->operation($query, 'query');
}
/**
* Execute a GraphQL mutation
*
* @param mixed $params,...
*/
protected function mutation(string $mutation, ...$params): ResponseInterface
{
$mutation = sprintf('mutation %s {%s}', $this->getName(), sprintf($mutation, ...$params));
return $this->operation($mutation, 'mutation');
}
/**
* Execute a GraphQL mutation with uploaded files
*
* @param mixed $params,...
*/
protected function mutationWithUpload(
string $mutation,
UploadedFileInterface $uploadedFile,
...$params
): ResponseInterface
{
$files = [1 => $uploadedFile];
$map = [1 => ['variables.file']];
$variables = ['file' => null];
$mutation = sprintf(
'mutation %s($file: Upload!) {%s}',
$this->getName(),
sprintf($mutation, ...$params),
);
return $this->operation($mutation, 'mutation', $variables, $files, $map);
}
/**
* Gets the response data array from the response object
*/
protected function getResponseData(ResponseInterface $response): array
{
$data = [];
$responseBody = $response->getBody()->__toString();
$responseCode = $response->getStatusCode();
if ($responseBody) {
$responseContents = json_decode($responseBody, true);
if (!$responseContents) {
throw new Exception(
'Unable to get a valid response body.
Response: (' . $responseCode . ') ' . $responseBody,
);
}
if (!isset($responseContents['data'])) {
throw new Exception(
'Response body does not include a "data" key.
Response: (' . $responseCode . ') ' . $responseBody,
);
}
$data = $responseContents['data'];
}
return $data;
}
/**
* Asserts that the response is as expected
*
* @param string[] $expected
*/
protected function assertResponseDataEquals(ResponseInterface $response, array $expected): void
{
$this->assertEquals(
$expected,
$this->getResponseData($response),
$response->getBody()->getContents(),
);
}
/**
* Asserts that the response contains the number of expected results
*/
protected function assertResponseCountEquals(
ResponseInterface $response,
string $field,
int $expectedCount
): void
{
$this->assertEquals($expectedCount, count($this->getResponseData($response)[$field]));
}
/**
* Asserts that the response has results
*/
protected function assertResponseHasResults(ResponseInterface $response): void
{
$this->assertNotEmpty($this->getResponseData($response));
}
/**
* Asserts that the response does not have any errors
*/
protected function assertResponseHasNoErrors(ResponseInterface $response): void
{
$responseContents = json_decode($response->getBody()->__toString(), true);
$errorMessage = isset($responseContents['errors'])
? $responseContents['errors'][0]['message']
: '';
$errorMessage .= isset($responseContents['errors'][0]['extensions']['fields'])
&& count($responseContents['errors'][0]['extensions']['fields']) > 0
? ' (' . implode(', ', $responseContents['errors'][0]['extensions']['fields']) . ')'
: '';
try {
$this->assertEmpty($errorMessage, $errorMessage);
} catch (ExpectationFailedException $e) {
throw new ExpectationFailedException(
'Failed response (' . $response->getStatusCode() . '): ' . $errorMessage,
null,
$e,
);
}
}
/**
* Asserts the HTTP status code from the response
*/
protected function assertResponseCode(ResponseInterface $response, int $expectedCode, string $message = ''): void
{
$statusCode = $response->getStatusCode();
$message = $message ?: $response->getBody()->getContents();
$this->assertEquals(
$expectedCode,
$statusCode,
\sprintf('HTTP status code of "%s" is expected "%s". %s', $statusCode, $expectedCode, $message),
);
}
} |
oojacoboo
changed the title
how to writes integration tests against graphql query
Writing integration tests against graphql query
Jun 12, 2022
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
there is any way to write integration tests aka (functional tests) against Graphql query for both Symfony & Laravel framework?
The text was updated successfully, but these errors were encountered: