├── .github
├── FUNDING.yml
└── workflows
│ ├── psalm.yml
│ └── run-tests.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── psalm.xml.dist
├── src
├── GraphQLClient.php
├── QueryBuilder.php
├── Scalar.php
├── Scalars
│ ├── BooleanType.php
│ ├── EnumType.php
│ ├── NullType.php
│ └── StringType.php
└── TestGraphQL.php
└── tests
├── FakeTestCase.php
├── GraphQLClientTest.php
├── QueryBuilderTest.php
├── TestCase.php
└── TestGraphQLTest.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [marvinrabe]
4 |
--------------------------------------------------------------------------------
/.github/workflows/psalm.yml:
--------------------------------------------------------------------------------
1 | name: Psalm
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.php'
7 | - 'psalm.xml.dist'
8 |
9 | jobs:
10 | psalm:
11 | name: psalm
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: '7.4'
20 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
21 | coverage: none
22 |
23 | - name: Cache composer dependencies
24 | uses: actions/cache@v2
25 | with:
26 | path: vendor
27 | key: composer-${{ hashFiles('composer.lock') }}
28 |
29 | - name: Run composer install
30 | run: composer install -n --prefer-dist
31 |
32 | - name: Run psalm
33 | run: ./vendor/bin/psalm --output-format=github
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | fail-fast: true
10 | matrix:
11 | php: [7.4, 8.0, 8.1]
12 |
13 | name: P${{ matrix.php }}
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v2
18 |
19 | - name: Setup PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | php-version: ${{ matrix.php }}
23 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
24 | coverage: none
25 |
26 | - name: Setup problem matchers
27 | run: |
28 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
29 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
30 | - name: Install dependencies
31 | run: composer install --prefer-dist --no-interaction
32 | - name: Execute tests
33 | run: vendor/bin/phpunit
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .php_cs
3 | .php_cs.cache
4 | .phpunit.result.cache
5 | build
6 | composer.lock
7 | coverage
8 | docs
9 | phpunit.xml
10 | psalm.xml
11 | vendor
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Marvin Rabe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQL Testing Helper for Laravel
2 |
3 | [](https://packagist.org/packages/marvinrabe/laravel-graphql-test)
4 | [](https://github.com/marvinrabe/laravel-graphql-test/actions?query=workflow%3ATests+branch%3Amaster)
5 | [](https://packagist.org/packages/marvinrabe/laravel-graphql-test)
6 |
7 | Elegant GraphQL testing utilities for Laravel. Works with any GraphQL library. Especially with [Lighthouse](https://lighthouse-php.com/).
8 |
9 | ## Installation
10 |
11 | You can install the package via composer:
12 |
13 | ```bash
14 | composer require --dev marvinrabe/laravel-graphql-test
15 | ```
16 |
17 | And then add the trait to your `TestCase` class:
18 |
19 | ```php
20 | query('account', ['id' => 123], ['id']);
46 | ```
47 |
48 | Note that this function returns an `\Illuminate\Foundation\Testing\TestResponse`. Therefore you might use any Laravel testing methods. For example:
49 |
50 | ```php
51 | $this->query('account', ['id' => 123], ['id'])
52 | ->assertSuccessful()
53 | ->assertJsonFragment([
54 | 'id' => 123
55 | ]);
56 | ```
57 |
58 | With nested resources:
59 |
60 | ```php
61 | $this->query('account', ['id' => 123], ['transactions' => ['id']]);
62 | ```
63 |
64 | Without a third argument it will be assumed that the second one is the selection set:
65 |
66 | ```php
67 | $this->query('accounts', ['id']);
68 | ```
69 |
70 | When you only pass the object name, you get the `GraphQLClient` instead of the Laravel `TestResponse`:
71 |
72 | ```php
73 | $this->query('accounts')->getGql();
74 | ```
75 |
76 | ### Mutations
77 |
78 | Same as queries:
79 |
80 | ```php
81 | $this->mutation('accounts')->getGql();
82 | $this->mutation('accounts', ['id']);
83 | $this->mutation('accounts', ['id' => 123]);
84 | ```
85 |
86 | ### Argument Order
87 |
88 | For simplicity you can find the correct argument order in the following table:
89 |
90 | | Method | Arguments | Returns |
91 | |---------:|----------------------------------:|--------------:|
92 | | query | (object) | GraphQLClient |
93 | | query | (object, selectionSet) | TestResponse |
94 | | query | (object, arguments, selectionSet) | TestResponse |
95 | | mutation | (object) | GraphQLClient |
96 | | mutation | (object, selectionSet) | TestResponse |
97 | | mutation | (object, arguments, selectionSet) | TestResponse |
98 |
99 | ### Enums
100 |
101 | Because PHP has no built in Enum support. You have to use the provided enum helper:
102 |
103 | ```php
104 | $this->query('accounts', ['status' => $this->enum('closed')], ['id']);
105 | ```
106 |
107 | Or create a `EnumType` manually:
108 |
109 | ```php
110 | $this->query('accounts', ['status' => new \MarvinRabe\LaravelGraphQLTest\Scalars\EnumType('closed')], ['id']);
111 | ```
112 |
113 | ### Headers
114 |
115 | You can add additional HTTP headers by using `withHeader` or `withHeaders` methods provided by Laravel. For example:
116 |
117 | $this->withHeaders(["Authorization" => "Bearer TOKEN"])->query('accounts', ['id']);
118 |
119 | If you always provide the same headers, you could define them on your TestCase.
120 |
121 | ```php
122 | class AccountsTest extends TestCase
123 | {
124 | protected $defaultHeaders = [
125 | "Authorization" => "Bearer TOKEN",
126 | ];
127 |
128 | // ...
129 | }
130 | ```
131 |
132 | ### Limitations
133 |
134 | The `QueryBuilder` provided by this library is not safe for use in production code. It is designed for ease of use and does not comply to the GraphQL specifications fully. Use it only for testing purposes! You have been warned.
135 |
136 | ## Testing
137 |
138 | ``` bash
139 | composer test
140 | ```
141 |
142 | ## License
143 |
144 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
145 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marvinrabe/laravel-graphql-test",
3 | "description": "Provides you with a simple GraphQL testing trait.",
4 | "keywords": [
5 | "php",
6 | "laravel",
7 | "graphql",
8 | "testing"
9 | ],
10 | "homepage": "https://github.com/marvinrabe/laravel-graphql-test",
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Marvin Rabe",
15 | "email": "marvin@rabe.pro",
16 | "homepage": "https://www.rabe.pro",
17 | "role": "Developer"
18 | }
19 | ],
20 | "require": {
21 | "php": ">=7.0.0",
22 | "illuminate/contracts": ">=8.0.0"
23 | },
24 | "require-dev": {
25 | "orchestra/testbench": "^6.0",
26 | "phpunit/phpunit": "^9.3",
27 | "vimeo/psalm": "^3.11"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "MarvinRabe\\LaravelGraphQLTest\\": "src"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "MarvinRabe\\LaravelGraphQLTest\\Tests\\": "tests"
37 | }
38 | },
39 | "scripts": {
40 | "psalm": "vendor/bin/psalm",
41 | "test": "vendor/bin/phpunit --colors=always",
42 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
43 | },
44 | "config": {
45 | "sort-packages": true
46 | },
47 | "minimum-stability": "dev",
48 | "prefer-stable": true
49 | }
50 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | ./src
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/psalm.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/GraphQLClient.php:
--------------------------------------------------------------------------------
1 | builder = $builder;
20 | $this->endpoint = $fun;
21 | }
22 |
23 | public static function query($object, $fun)
24 | {
25 | return new self(new QueryBuilder("query", $object), $fun);
26 | }
27 |
28 | public static function mutation($object, $fun)
29 | {
30 | return new self(new QueryBuilder("mutation", $object), $fun);
31 | }
32 |
33 | public function setArguments(array $arguments = null)
34 | {
35 | $this->builder->setArguments($arguments ?? []);
36 |
37 | return $this;
38 | }
39 |
40 | public function setSelectionSet(array $selectionSet = null)
41 | {
42 | $this->builder->setSelectionSet($selectionSet ?? []);
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * @psalm-suppress UndefinedDocblockClass
49 | * @return \Illuminate\Testing\TestResponse
50 | */
51 | public function getData()
52 | {
53 | return ($this->endpoint)($this->getGql());
54 | }
55 |
56 | public function getGql(): string
57 | {
58 | return $this->builder->getGql();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/QueryBuilder.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
29 | $this->object = $object;
30 | }
31 |
32 | public function setSelectionSet(array $selectionSet)
33 | {
34 | $this->selectionSet = $selectionSet;
35 |
36 | return $this;
37 | }
38 |
39 | public function setArguments(array $arguments)
40 | {
41 | $this->arguments = $arguments;
42 |
43 | return $this;
44 | }
45 |
46 | public function getGql(): string
47 | {
48 | return sprintf('%s { %s }', $this->operation, implode("", [
49 | $this->object,
50 | $this->arguments(),
51 | $this->selectionSet(),
52 | ]));
53 | }
54 |
55 | protected function arguments()
56 | {
57 | if (count($this->arguments) == 0) {
58 | return '';
59 | }
60 |
61 | return sprintf('(%s)', $this->formatArgument($this->arguments));
62 | }
63 |
64 | protected function formatArgument($arguments)
65 | {
66 | array_walk($arguments, function (&$value, $key) {
67 | $value = sprintf('%s: %s', $key, $this->formatValue($value));
68 | });
69 |
70 | return implode(', ', $arguments);
71 | }
72 |
73 | protected function formatValue($value)
74 | {
75 | if (is_array($value) && $this->is_assoc($value)) {
76 | return sprintf("{%s}", $this->formatArgument($value));
77 | } elseif (is_array($value)) {
78 | return $this->formatArray($value);
79 | }
80 |
81 | return $this->formatScalar($value);
82 | }
83 |
84 | protected function is_assoc(array $array)
85 | {
86 | return count(array_filter(array_keys($array), 'is_string')) > 0;
87 | }
88 |
89 | protected function formatArray(array $value)
90 | {
91 | return sprintf('[%s]', implode(', ', array_map(function ($value) {
92 | return $this->formatValue($value);
93 | }, $value)));
94 | }
95 |
96 | protected function formatScalar($scalar)
97 | {
98 | foreach ($this->convertScalars as $scalarType) {
99 | /**
100 | * @var class-string $scalarType
101 | */
102 | if ($scalarType::match($scalar)) {
103 | return new $scalarType($scalar);
104 | }
105 | }
106 |
107 | return (string) $scalar;
108 | }
109 |
110 | protected function selectionSet()
111 | {
112 | if (count($this->selectionSet) == 0) {
113 | return '';
114 | }
115 |
116 | return $this->formatSelectionSet($this->selectionSet);
117 | }
118 |
119 | protected function formatSelectionSet($selectionSet)
120 | {
121 | array_walk($selectionSet, function (&$value, $key) {
122 | if (is_array($value)) {
123 | $value = sprintf('%s %s', $key, $this->formatSelectionSet($value));
124 | }
125 | });
126 |
127 | return sprintf("{\n%s\n}", implode("\n", $selectionSet));
128 | }
129 |
130 | public function __toString()
131 | {
132 | return $this->getGql();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Scalar.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function __toString()
17 | {
18 | return $this->value ? 'true' : 'false';
19 | }
20 |
21 | public static function match($value): bool
22 | {
23 | return is_bool($value);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Scalars/EnumType.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function __toString()
17 | {
18 | return $this->value;
19 | }
20 |
21 | public static function match($value): bool
22 | {
23 | return false;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Scalars/NullType.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function __toString()
17 | {
18 | $escaped = str_replace('\\', '\\\\', $this->value);
19 | $escaped = str_replace('"', '\"', $escaped);
20 |
21 | return sprintf('"%s"', $escaped);
22 | }
23 |
24 | public static function match($value): bool
25 | {
26 | return is_string($value);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/TestGraphQL.php:
--------------------------------------------------------------------------------
1 | postJson(
27 | $this->graphQLEndpoint ?? 'graphql',
28 | [
29 | 'query' => $query,
30 | ]
31 | );
32 | });
33 |
34 | return $this->prepareClient($client, $arguments, $selection);
35 | }
36 |
37 | /**
38 | * Returns a TestGraphQL client. If no arguments and selection is provided, it will query the server directly.
39 | * @param string $object GraphQL query name
40 | * @param array|null $arguments Specifies arguments send to the server. When selection is null it will be used as a selection set instead.
41 | * @param array|null $selection Specifies Selection set send to the server.
42 | * @return GraphQLClient|\Illuminate\Testing\TestResponse
43 | */
44 | public function mutation(string $object, $arguments = null, $selection = null)
45 | {
46 | $client = GraphQLClient::mutation($object, function ($query) {
47 | return $this->postJson(
48 | $this->graphQLEndpoint ?? 'graphql',
49 | [
50 | 'query' => $query,
51 | ]
52 | );
53 | });
54 |
55 | return $this->prepareClient($client, $arguments, $selection);
56 | }
57 |
58 | private function prepareClient(GraphQLClient $client, $arguments, $selection)
59 | {
60 | if (! is_null($arguments) && is_null($selection)) {
61 | $client->setSelectionSet($arguments);
62 |
63 | return $client->getData();
64 | }
65 |
66 | if (! is_null($arguments) && ! is_null($selection)) {
67 | $client->setArguments($arguments);
68 | $client->setSelectionSet($selection);
69 |
70 | return $client->getData();
71 | }
72 |
73 | return $client;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/FakeTestCase.php:
--------------------------------------------------------------------------------
1 | uri = $uri;
18 | $this->data = $data;
19 | $this->headers = $headers;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/GraphQLClientTest.php:
--------------------------------------------------------------------------------
1 | setArguments(null);
14 | $client->setSelectionSet(null);
15 |
16 | $this->assertEquals('query { foo }', $client->getGql());
17 | }
18 |
19 | public function testReturnsQuery()
20 | {
21 | $client = GraphQLClient::query('foo', function () {
22 | });
23 | $client->setArguments(['id' => 123]);
24 | $client->setSelectionSet(['bar']);
25 |
26 | $this->assertEquals("query { foo(id: 123){\nbar\n} }", $client->getGql());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/QueryBuilderTest.php:
--------------------------------------------------------------------------------
1 | setArguments([]);
13 | $qb->setSelectionSet([]);
14 |
15 | $this->assertEquals("query { foo }", $qb->getGql());
16 | }
17 |
18 | public function testFormatSelectionSet()
19 | {
20 | $qb = new QueryBuilder('query', 'acme');
21 | $qb->setSelectionSet([
22 | 'foo',
23 | 'bar',
24 | ]);
25 |
26 | $this->assertEquals("query { acme{\nfoo\nbar\n} }", $qb->getGql());
27 | }
28 |
29 | public function testFormatNestedQueries()
30 | {
31 | $qb = new QueryBuilder('query', 'acme');
32 | $qb->setSelectionSet([
33 | 'foo' => ['bar'],
34 | ]);
35 |
36 | $this->assertEquals("query { acme{\nfoo {\nbar\n}\n} }", $qb->getGql());
37 | }
38 |
39 | public function testFormatStringAttributes()
40 | {
41 | $qb = new QueryBuilder('mutation', 'foo');
42 | $qb->setArguments([
43 | 'bar' => 'Jonathan "Johnny" Johnson',
44 | ]);
45 |
46 | $this->assertEquals('mutation { foo(bar: "Jonathan \"Johnny\" Johnson") }', $qb->getGql());
47 | }
48 |
49 | public function testFormatsBoolean()
50 | {
51 | $qb = new QueryBuilder('mutation', 'foo');
52 | $qb->setArguments([
53 | 'bar' => true,
54 | ]);
55 |
56 | $this->assertEquals('mutation { foo(bar: true) }', $qb->getGql());
57 | }
58 |
59 | public function testFormatsInputArguments()
60 | {
61 | $qb = new QueryBuilder('mutation', 'foo');
62 | $qb->setArguments([
63 | 'bar' => ['a' => 1, 'b' => 2, 'c' => 3],
64 | ]);
65 |
66 | $this->assertEquals('mutation { foo(bar: {a: 1, b: 2, c: 3}) }', $qb->getGql());
67 | }
68 |
69 | public function testFormatsArrayArguments()
70 | {
71 | $qb = new QueryBuilder('mutation', 'foo');
72 | $qb->setArguments([
73 | 'bar' => [1, 2, 3],
74 | ]);
75 |
76 | $this->assertEquals('mutation { foo(bar: [1, 2, 3]) }', $qb->getGql());
77 | }
78 |
79 | public function testFormatInputsInArrayArgument()
80 | {
81 | $qb = new QueryBuilder('mutation', 'foo');
82 | $qb->setArguments([
83 | 'bar' => [['a' => 1], ['a' => 2], ['a' => 3]],
84 | ]);
85 |
86 | $this->assertEquals('mutation { foo(bar: [{a: 1}, {a: 2}, {a: 3}]) }', $qb->getGql());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | enum('payed');
15 |
16 | $this->assertInstanceOf(EnumType::class, $result);
17 | $this->assertEquals('payed', (string) $result);
18 | }
19 |
20 | public function testQueryOneArgument()
21 | {
22 | $testCase = new FakeTestCase();
23 |
24 | $result = $testCase->query('accounts');
25 |
26 | $this->assertInstanceOf(GraphQLClient::class, $result);
27 | }
28 |
29 | public function testQueryTwoArguments()
30 | {
31 | $testCase = new FakeTestCase();
32 |
33 | $testCase->query('accounts', ['id']);
34 |
35 | $this->assertEquals('graphql', $testCase->uri);
36 | $this->assertEquals(['query' => "query { accounts{\nid\n} }"], $testCase->data);
37 | }
38 |
39 | public function testQueryThreeArguments()
40 | {
41 | $testCase = new FakeTestCase();
42 |
43 | $testCase->query('accounts', ['id' => 123], ['id']);
44 |
45 | $this->assertEquals('graphql', $testCase->uri);
46 | $this->assertEquals(['query' => "query { accounts(id: 123){\nid\n} }"], $testCase->data);
47 | }
48 |
49 | public function testMutationOneArgument()
50 | {
51 | $testCase = new FakeTestCase();
52 |
53 | $result = $testCase->mutation('createAccount');
54 |
55 | $this->assertInstanceOf(GraphQLClient::class, $result);
56 | }
57 |
58 | public function testMutationTwoArguments()
59 | {
60 | $testCase = new FakeTestCase();
61 |
62 | $testCase->mutation('createAccount', ['id']);
63 |
64 | $this->assertEquals('graphql', $testCase->uri);
65 | $this->assertEquals(['query' => "mutation { createAccount{\nid\n} }"], $testCase->data);
66 | }
67 |
68 | public function testMutationThreeArguments()
69 | {
70 | $testCase = new FakeTestCase();
71 |
72 | $testCase->mutation('createAccount', ['id' => 123], ['id']);
73 |
74 | $this->assertEquals('graphql', $testCase->uri);
75 | $this->assertEquals(['query' => "mutation { createAccount(id: 123){\nid\n} }"], $testCase->data);
76 | }
77 |
78 | public function testMutationWithEmptyArrays()
79 | {
80 | $testCase = new FakeTestCase();
81 |
82 | $testCase->mutation('logout', [], []);
83 |
84 | $this->assertEquals(['query' => "mutation { logout }"], $testCase->data);
85 | }
86 |
87 | public function testMutationWithOneEmptyArrayArguments()
88 | {
89 | $testCase = new FakeTestCase();
90 |
91 | $testCase->mutation('logout', [], ['success']);
92 |
93 | $this->assertEquals(['query' => "mutation { logout{\nsuccess\n} }"], $testCase->data);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------