Skip to content

Commit fec0471

Browse files
committed
test: feature: to jsonapi resource
1 parent 686281d commit fec0471

20 files changed

+665
-7
lines changed

phpunit.xml.dist

-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
<testsuite name="Unit">
1212
<directory>tests/Unit</directory>
1313
</testsuite>
14-
<!--
1514
<testsuite name="Feature">
1615
<directory>tests/Feature</directory>
1716
</testsuite>
18-
-->
1917
</testsuites>
2018

2119
<coverage>

src/Concerns/AsRelationship.php renamed to src/Concerns/Relationize.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@
55
use Ark4ne\JsonApi\Resource\Relationship;
66
use Closure;
77

8-
trait AsRelationship
8+
trait Relationize
99
{
10+
/**
11+
* Transform JSON:API resource as relationship
12+
*
13+
* @param iterable|\Closure $links
14+
* @param iterable|\Closure $meta
15+
*
16+
* @return \Ark4ne\JsonApi\Resource\Relationship
17+
*/
1018
public function asRelationship(
1119
iterable|Closure $links = [],
1220
iterable|Closure $meta = []

src/Concerns/ToResponse.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Ark4ne\JsonApi\Resource\Concerns;
4+
5+
use Illuminate\Http\JsonResponse;
6+
use Illuminate\Http\Request;
7+
8+
trait ToResponse
9+
{
10+
/**
11+
* @param Request $request
12+
*/
13+
public function toResponse($request): JsonResponse
14+
{
15+
return parent
16+
::toResponse($request)
17+
->header('Content-type', 'application/vnd.api+json');
18+
}
19+
}

src/JsonApiCollection.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace Ark4ne\JsonApi\Resource;
44

5-
use Ark4ne\JsonApi\Resource\Concerns\AsRelationship;
5+
use Ark4ne\JsonApi\Resource\Concerns;
66
use Ark4ne\JsonApi\Resource\Support\With;
77
use Illuminate\Http\Resources\Json\JsonResource;
88
use Illuminate\Http\Resources\Json\ResourceCollection;
99

1010
class JsonApiCollection extends ResourceCollection implements Resourceable
1111
{
12-
use AsRelationship;
12+
use Concerns\Relationize,
13+
Concerns\ToResponse;
1314

1415
public $collects;
1516

src/JsonApiResource.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
abstract class JsonApiResource extends JsonResource implements Resourceable
99
{
10-
use Concerns\AsRelationship,
10+
use Concerns\Relationize,
1111
Concerns\Identifier,
1212
Concerns\Attributes,
1313
Concerns\Relationships,
1414
Concerns\Links,
15-
Concerns\Meta;
15+
Concerns\Meta,
16+
Concerns\ToResponse;
1617

1718
public function toArray($request, bool $minimal = false): array
1819
{

tests/Feature/ResourceTest.php

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
namespace Test\Feature;
4+
5+
use DateTimeInterface;
6+
use Illuminate\Foundation\Testing\RefreshDatabase;
7+
use Illuminate\Http\Request;
8+
use Test\app\Http\Resources\CommentResource;
9+
use Test\app\Http\Resources\PostResource;
10+
use Test\app\Http\Resources\UserResource;
11+
use Test\app\Models\Comment;
12+
use Test\app\Models\Post;
13+
use Test\app\Models\User;
14+
use Test\Support\UseLocalApp;
15+
use Test\TestCase;
16+
17+
class ResourceTest extends TestCase
18+
{
19+
use RefreshDatabase, UseLocalApp;
20+
21+
public function setUp(): void
22+
{
23+
parent::setUp();
24+
$this->useLocalApp();
25+
}
26+
27+
public function testShowBasic()
28+
{
29+
$user = $this->dataSeed();
30+
31+
$response = $this->get("user/{$user->id}");
32+
33+
$response->assertExactJson($this->getJsonResult($user));
34+
}
35+
36+
public function testShowWithAttributes()
37+
{
38+
$user = $this->dataSeed();
39+
40+
$response = $this->get("user/{$user->id}?fields[user]=name");
41+
42+
$response->assertExactJson($this->getJsonResult($user, ['name']));
43+
}
44+
45+
public function testShowWithRelationshipsPosts()
46+
{
47+
$user = $this->dataSeed();
48+
49+
$response = $this->get("user/{$user->id}?include=posts");
50+
51+
$response->assertExactJson($this->getJsonResult($user, null, ['posts']));
52+
}
53+
54+
public function testShowWithRelationshipsComments()
55+
{
56+
$user = $this->dataSeed();
57+
58+
$response = $this->get("user/{$user->id}?include=comments");
59+
60+
$response->assertExactJson($this->getJsonResult($user, null, ['comments']));
61+
}
62+
63+
public function testShowWithRelationshipsPostsComments()
64+
{
65+
$user = $this->dataSeed();
66+
67+
$response = $this->get("user/{$user->id}?include=posts,comments");
68+
69+
$response->assertExactJson($this->getJsonResult($user, null, ['posts', 'comments']));
70+
}
71+
72+
public function testShowWithRelationshipsDeepInclude()
73+
{
74+
$user = $this->dataSeed();
75+
76+
$expected = $this->getJsonResult($user, null, ['posts']);
77+
$expected['included'][] = $expected['data'];
78+
79+
$response = $this->get("user/{$user->id}?include=posts.user");
80+
81+
$response->assertExactJson($expected);
82+
83+
$response = $this->get("user/{$user->id}?include=posts.user.posts.user.posts.user");
84+
85+
$response->assertExactJson($expected);
86+
}
87+
88+
private function dataSeed()
89+
{
90+
/** @var User $user */
91+
$user = User::factory()->create();
92+
$posts = Post::factory()->for($user)->count(3)->create();
93+
$users = User::factory()->count(9)->create();
94+
foreach ($posts as $post) {
95+
foreach ($users->random(5) as $u) {
96+
Comment::factory()->for($post)->for($u)->create();
97+
}
98+
}
99+
100+
return $user;
101+
}
102+
103+
private function getJsonResult(User $user, ?array $attributes = null, ?array $relationships = null)
104+
{
105+
$request = new Request([
106+
...($attributes !== null ? ['fields' => ['user' => implode(',', $attributes)]] : []),
107+
...($relationships !== null ? ['include' => implode(',', $relationships)] : []),
108+
]);
109+
110+
return array_filter([
111+
'data' => [
112+
'id' => $user->id,
113+
'type' => 'user',
114+
'attributes' => array_filter(array_intersect_key([
115+
'name' => $user->name,
116+
'email' => $user->email,
117+
], array_fill_keys($attributes ?? ['name', 'email'], true))),
118+
'relationships' => [
119+
'posts' => array_filter([
120+
'data' => $user->posts->map(fn(Post $post) => ['type' => 'post', 'id' => $post->id])->all(),
121+
'links' => [
122+
'self' => "https://api.example.com/user/{$user->id}/relationships/posts",
123+
'related' => "https://api.example.com/user/{$user->id}/posts",
124+
]
125+
]),
126+
'comments' => array_filter([
127+
// when loaded only
128+
'data' => in_array('comments', $relationships ?? [])
129+
? $user->comments->map(fn(Comment $comment) => [
130+
'type' => 'comment',
131+
'id' => $comment->id
132+
])->all()
133+
: null,
134+
'links' => [
135+
'self' => "https://api.example.com/user/{$user->id}/relationships/comments",
136+
'related' => "https://api.example.com/user/{$user->id}/comments",
137+
]
138+
]),
139+
],
140+
'meta' => [
141+
'created_at' => $user->created_at->format(DateTimeInterface::ATOM),
142+
'updated_at' => $user->updated_at->format(DateTimeInterface::ATOM),
143+
],
144+
],
145+
'included' => array_filter([
146+
...(in_array('posts', $relationships ?? [])
147+
? $user->posts->mapInto(PostResource::class)->map->toArray($request)
148+
: []),
149+
...(in_array('comments', $relationships ?? [])
150+
? $user->comments->mapInto(CommentResource::class)->map->toArray($request)
151+
: []),
152+
])
153+
]);
154+
}
155+
}

tests/Support/UseLocalApp.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Test\Support;
4+
5+
trait UseLocalApp
6+
{
7+
public function useLocalApp()
8+
{
9+
$this->loadMigrationsFrom(__DIR__ . '/../app/migrations.php');
10+
}
11+
12+
protected function defineRoutes($router)
13+
{
14+
include __DIR__ . '/../app/routes.php';
15+
}
16+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Test\app\Factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
use Test\app\Models\Comment;
7+
8+
class CommentFactory extends Factory
9+
{
10+
protected $model = Comment::class;
11+
12+
public function definition()
13+
{
14+
return [
15+
'content' => $this->faker->text
16+
];
17+
}
18+
}

tests/app/Factories/PostFactory.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Test\app\Factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
use Test\app\Models\Post;
7+
8+
class PostFactory extends Factory
9+
{
10+
protected $model = Post::class;
11+
12+
public function definition()
13+
{
14+
return [
15+
'title' => $this->faker->title,
16+
'content' => $this->faker->text,
17+
];
18+
}
19+
}

tests/app/Factories/UserFactory.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Test\app\Factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
use Illuminate\Support\Facades\Hash;
7+
use Test\app\Models\User;
8+
9+
class UserFactory extends Factory
10+
{
11+
protected $model = User::class;
12+
13+
public function definition()
14+
{
15+
return [
16+
'name' => $this->faker->name,
17+
'email' => $this->faker->email,
18+
'password' => Hash::make('password')
19+
];
20+
}
21+
}

0 commit comments

Comments
 (0)