graphiti 2.0.0.beta.2 → 2.0.0.beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -86
  3. data/.github/workflows/docs.yml +60 -0
  4. data/.github/workflows/release.yml +8 -8
  5. data/.gitignore +7 -0
  6. data/.npmrc +9 -0
  7. data/.standard.yml +4 -1
  8. data/Appraisals +33 -32
  9. data/CHANGELOG.md +41 -0
  10. data/README.md +13 -2
  11. data/UPGRADING.md +2 -68
  12. data/docs/concepts/backends-and-models.md +122 -0
  13. data/docs/concepts/endpoints.md +183 -0
  14. data/docs/concepts/links.md +212 -0
  15. data/docs/concepts/overview.md +80 -0
  16. data/docs/concepts/persisting.md +376 -0
  17. data/docs/concepts/relationships.md +527 -0
  18. data/docs/concepts/resources.md +677 -0
  19. data/docs/getting-started/first-api.md +289 -0
  20. data/docs/getting-started/installation.md +185 -0
  21. data/docs/intro.md +307 -0
  22. data/docs/js/authentication.md +63 -0
  23. data/docs/js/ddau.md +20 -0
  24. data/docs/js/extra-params.md +41 -0
  25. data/docs/js/index.md +112 -0
  26. data/docs/js/installation.md +120 -0
  27. data/docs/js/middleware.md +72 -0
  28. data/docs/js/models.md +202 -0
  29. data/docs/js/reads.md +494 -0
  30. data/docs/js/state-syncing.md +100 -0
  31. data/docs/js/writes.md +373 -0
  32. data/docs/reference/vandal.md +63 -0
  33. data/docs/reference/why.md +13 -0
  34. data/docs/topics/authorization.md +155 -0
  35. data/docs/topics/caching.md +55 -0
  36. data/docs/topics/customizing-sideloads.md +156 -0
  37. data/docs/topics/debugging.md +216 -0
  38. data/docs/topics/error-handling.md +210 -0
  39. data/docs/topics/etags.md +46 -0
  40. data/docs/topics/hopping-relationships.md +149 -0
  41. data/docs/topics/json-attributes.md +77 -0
  42. data/docs/topics/openstruct-models.md +50 -0
  43. data/docs/topics/remote-resources.md +291 -0
  44. data/docs/topics/testing.md +894 -0
  45. data/docs/topics/without-activerecord.md +324 -0
  46. data/docs/tutorial/index.md +58 -0
  47. data/docs/tutorial/step_0.md +107 -0
  48. data/docs/tutorial/step_1.md +199 -0
  49. data/docs/tutorial/step_2.md +312 -0
  50. data/docs/tutorial/step_3.md +142 -0
  51. data/docs/tutorial/step_4.md +135 -0
  52. data/docs/tutorial/step_5.md +69 -0
  53. data/docs/tutorial/step_6.md +82 -0
  54. data/docs/tutorial/step_7.md +205 -0
  55. data/docs/tutorial/step_8.md +128 -0
  56. data/docs/tutorial/step_9.md +171 -0
  57. data/docs/upgrading.md +265 -0
  58. data/gemfiles/rails_7_1.gemfile +4 -3
  59. data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
  60. data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
  61. data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
  62. data/graphiti.gemspec +7 -5
  63. data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
  64. data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
  65. data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
  66. data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
  67. data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
  68. data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
  69. data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
  70. data/lib/graphiti/configuration.rb +2 -2
  71. data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
  72. data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
  73. data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
  74. data/lib/graphiti/error_serializers/validation.rb +143 -0
  75. data/lib/graphiti/errors.rb +4 -23
  76. data/lib/graphiti/query.rb +1 -1
  77. data/lib/graphiti/rails/context.rb +33 -0
  78. data/lib/graphiti/rails/controller.rb +41 -0
  79. data/lib/graphiti/rails/debugging.rb +18 -0
  80. data/lib/graphiti/rails/exception_handlers.rb +77 -0
  81. data/lib/graphiti/rails/railtie.rb +139 -0
  82. data/lib/graphiti/rails/responders.rb +21 -0
  83. data/lib/graphiti/rails/test_helpers.rb +22 -0
  84. data/lib/graphiti/rails.rb +47 -29
  85. data/lib/graphiti/resource/configuration.rb +1 -0
  86. data/lib/graphiti/resource/interface.rb +2 -2
  87. data/lib/graphiti/resource/persistence.rb +14 -2
  88. data/lib/graphiti/resource/remote.rb +2 -2
  89. data/lib/graphiti/resource/sideloading.rb +1 -1
  90. data/lib/graphiti/resource.rb +13 -1
  91. data/lib/graphiti/responders.rb +7 -20
  92. data/lib/graphiti/schema.rb +5 -1
  93. data/lib/graphiti/schema_diff.rb +4 -0
  94. data/lib/graphiti/scope.rb +45 -37
  95. data/lib/graphiti/serializer.rb +6 -0
  96. data/lib/graphiti/sideload/belongs_to.rb +38 -5
  97. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
  98. data/lib/graphiti/sideload.rb +54 -35
  99. data/lib/graphiti/spec_helpers/errors.rb +73 -0
  100. data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
  101. data/lib/graphiti/spec_helpers/helpers.rb +107 -0
  102. data/lib/graphiti/spec_helpers/node.rb +88 -0
  103. data/lib/graphiti/spec_helpers/rspec.rb +147 -0
  104. data/lib/graphiti/spec_helpers.rb +53 -0
  105. data/lib/graphiti/util/include_params.rb +2 -2
  106. data/lib/graphiti/util/persistence.rb +10 -11
  107. data/lib/graphiti/util/serializer_relationships.rb +41 -5
  108. data/lib/graphiti/version.rb +1 -1
  109. data/lib/graphiti-rails.rb +11 -0
  110. data/lib/graphiti.rb +34 -10
  111. data/lib/graphiti_errors.rb +11 -0
  112. data/lib/graphiti_spec_helpers/rspec.rb +3 -0
  113. data/lib/graphiti_spec_helpers.rb +11 -0
  114. data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
  115. data/package-lock.json +6199 -0
  116. data/package.json +5 -4
  117. data/website/.gitignore +20 -0
  118. data/website/README.md +43 -0
  119. data/website/docusaurus.config.js +141 -0
  120. data/website/package-lock.json +19474 -0
  121. data/website/package.json +46 -0
  122. data/website/sidebars.js +82 -0
  123. data/website/src/css/custom.css +58 -0
  124. data/website/src/pages/markdown-page.mdx +7 -0
  125. data/website/static/.nojekyll +0 -0
  126. data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
  127. data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
  128. data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
  129. data/website/static/1.13/2019/10/14/tutorial.html +198 -0
  130. data/website/static/1.13/CNAME +1 -0
  131. data/website/static/1.13/README.md +16 -0
  132. data/website/static/1.13/assets/css/syntax.css +60 -0
  133. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  134. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  135. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  136. data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
  137. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  138. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  139. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  140. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  141. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
  142. data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
  143. data/website/static/1.13/assets/img/backend.gif +0 -0
  144. data/website/static/1.13/assets/img/conformity.png +0 -0
  145. data/website/static/1.13/assets/img/error_payload.png +0 -0
  146. data/website/static/1.13/assets/img/gh.png +0 -0
  147. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  148. data/website/static/1.13/assets/img/logo-500.png +0 -0
  149. data/website/static/1.13/assets/img/logo.png +0 -0
  150. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  151. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  152. data/website/static/1.13/assets/img/persist.jpg +0 -0
  153. data/website/static/1.13/assets/img/resource.gif +0 -0
  154. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  155. data/website/static/1.13/assets/img/rest1.gif +0 -0
  156. data/website/static/1.13/assets/img/rest2.gif +0 -0
  157. data/website/static/1.13/assets/img/rest3.gif +0 -0
  158. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  159. data/website/static/1.13/assets/img/why.png +0 -0
  160. data/website/static/1.13/assets/js/highlight.pack.js +2 -0
  161. data/website/static/1.13/assets/main.css +15518 -0
  162. data/website/static/1.13/assets/main.css.map +1 -0
  163. data/website/static/1.13/bin/bundle +109 -0
  164. data/website/static/1.13/bin/jekyll +27 -0
  165. data/website/static/1.13/bin/kramdown +27 -0
  166. data/website/static/1.13/bin/listen +27 -0
  167. data/website/static/1.13/bin/rake +27 -0
  168. data/website/static/1.13/bin/rougify +27 -0
  169. data/website/static/1.13/bin/safe_yaml +27 -0
  170. data/website/static/1.13/bin/sass +27 -0
  171. data/website/static/1.13/bin/sass-convert +27 -0
  172. data/website/static/1.13/bin/scss +27 -0
  173. data/website/static/1.13/blog.html +259 -0
  174. data/website/static/1.13/cheatsheet.html +316 -0
  175. data/website/static/1.13/cookbooks/authorization.md +0 -0
  176. data/website/static/1.13/cookbooks/caching.md +0 -0
  177. data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
  178. data/website/static/1.13/cookbooks/etags.md +0 -0
  179. data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
  180. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  181. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  182. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  183. data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
  184. data/website/static/1.13/features.html +249 -0
  185. data/website/static/1.13/feed.xml +106 -0
  186. data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
  187. data/website/static/1.13/guides/concepts/debugging.html +440 -0
  188. data/website/static/1.13/guides/concepts/endpoints.html +432 -0
  189. data/website/static/1.13/guides/concepts/error-handling.html +396 -0
  190. data/website/static/1.13/guides/concepts/links.html +501 -0
  191. data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
  192. data/website/static/1.13/guides/concepts/resources.html +2176 -0
  193. data/website/static/1.13/guides/concepts/testing.html +1469 -0
  194. data/website/static/1.13/guides/getting-started/installation.html +420 -0
  195. data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
  196. data/website/static/1.13/guides/index.html +269 -0
  197. data/website/static/1.13/guides/overview.html +325 -0
  198. data/website/static/1.13/guides/upgrading-2-0.html +193 -0
  199. data/website/static/1.13/guides/upgrading.html +314 -0
  200. data/website/static/1.13/guides/vandal.html +282 -0
  201. data/website/static/1.13/guides/why.html +1121 -0
  202. data/website/static/1.13/index.html +72 -0
  203. data/website/static/1.13/js/authentication.html +295 -0
  204. data/website/static/1.13/js/ddau.html +238 -0
  205. data/website/static/1.13/js/extra-params.html +270 -0
  206. data/website/static/1.13/js/index.html +321 -0
  207. data/website/static/1.13/js/installation.html +637 -0
  208. data/website/static/1.13/js/introduction.html +257 -0
  209. data/website/static/1.13/js/middleware.html +318 -0
  210. data/website/static/1.13/js/reads/fieldsets.html +271 -0
  211. data/website/static/1.13/js/reads/filtering.html +289 -0
  212. data/website/static/1.13/js/reads/includes.html +260 -0
  213. data/website/static/1.13/js/reads/index.html +497 -0
  214. data/website/static/1.13/js/reads/nested-queries.html +353 -0
  215. data/website/static/1.13/js/reads/pagination.html +260 -0
  216. data/website/static/1.13/js/reads/sorting.html +265 -0
  217. data/website/static/1.13/js/reads/statistics.html +289 -0
  218. data/website/static/1.13/js/state-syncing.html +340 -0
  219. data/website/static/1.13/js/writes/deferred.html +296 -0
  220. data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
  221. data/website/static/1.13/js/writes/index.html +391 -0
  222. data/website/static/1.13/js/writes/nested.html +330 -0
  223. data/website/static/1.13/js/writes/validations.html +272 -0
  224. data/website/static/1.13/quickstart.html +660 -0
  225. data/website/static/1.13/template +161 -0
  226. data/website/static/1.13/tutorial/index.html +250 -0
  227. data/website/static/1.13/tutorial/step_0.html +292 -0
  228. data/website/static/1.13/tutorial/step_1.html +517 -0
  229. data/website/static/1.13/tutorial/step_2.html +481 -0
  230. data/website/static/1.13/tutorial/step_3.html +323 -0
  231. data/website/static/1.13/tutorial/step_4.html +318 -0
  232. data/website/static/1.13/tutorial/step_5.html +265 -0
  233. data/website/static/1.13/tutorial/step_6.html +276 -0
  234. data/website/static/1.13/tutorial/step_7.html +390 -0
  235. data/website/static/1.13/tutorial/step_8.html +316 -0
  236. data/website/static/1.13/tutorial/step_9.html +365 -0
  237. data/website/static/assets/img/error_payload.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  239. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  240. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  242. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  243. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  244. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  245. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  246. data/website/static/assets/img/meta_total_count.png +0 -0
  247. data/website/static/img/docusaurus-social-card.jpg +0 -0
  248. data/website/static/img/docusaurus.png +0 -0
  249. data/website/static/img/favicon.ico +0 -0
  250. data/website/static/img/logo.png +0 -0
  251. data/website/static/img/logo.svg +1 -0
  252. data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
  253. data/website/static/img/undraw_docusaurus_react.svg +170 -0
  254. data/website/static/img/undraw_docusaurus_tree.svg +40 -0
  255. metadata +245 -46
  256. data/gemfiles/rails_6.gemfile +0 -18
  257. data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
  258. data/gemfiles/rails_7.gemfile +0 -18
  259. data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
  260. data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
  261. data/lib/graphiti/railtie.rb +0 -121
  262. /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
  263. /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
  264. /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
  265. /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
  266. /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
  267. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
  268. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
  269. /data/{deprecated_generators → lib/generators}/graphiti/templates/update_request_spec.rb.erb +0 -0
@@ -0,0 +1,1469 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css?family=Boogaloo" rel="stylesheet">
10
+
11
+ <link rel="apple-touch-icon" sizes="180x180" href="/1.13/assets/favicons/apple-touch-icon.png">
12
+ <link rel="icon" type="image/png" sizes="32x32" href="/1.13/assets/favicons/favicon-32x32.png">
13
+ <link rel="icon" type="image/png" sizes="16x16" href="/1.13/assets/favicons/favicon-16x16.png">
14
+ <link rel="manifest" href="/1.13/assets/favicons/site.webmanifest">
15
+ <link rel="mask-icon" href="/1.13/assets/favicons/safari-pinned-tab.svg" color="#F86DA7">
16
+ <link rel="shortcut icon" href="/1.13/assets/favicons/favicon.ico">
17
+ <meta name="msapplication-TileColor" content="#F86DA7">
18
+ <meta name="msapplication-config" content="/1.13/assets/favicons/browserconfig.xml">
19
+ <meta name="theme-color" content="#F86DA7">
20
+
21
+ <title>Graphiti</title>
22
+ <meta name="description" content="Stylish Graph APIs">
23
+
24
+ <link rel="stylesheet" href="/1.13/assets/main.css?ref=wh4t3v45">
25
+ <link rel="alternate" type="application/rss+xml" title="Graphiti" href="/1.13/feed.xml">
26
+
27
+ <!-- Global site tag (gtag.js) - Google Analytics -->
28
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-127904727-1"></script>
29
+ <script>
30
+ window.dataLayer = window.dataLayer || [];
31
+ function gtag(){dataLayer.push(arguments);}
32
+ gtag('js', new Date());
33
+
34
+ gtag('config', 'UA-127904727-1');
35
+ </script>
36
+
37
+ <!-- javascript -->
38
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
39
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
40
+
41
+
42
+ </head>
43
+
44
+
45
+ <body>
46
+ <main class="page-content" aria-label="Content">
47
+ <div class="wrapper">
48
+ <header class="navbar navbar-inverse normal" role="banner">
49
+ <div class="container">
50
+ <div class="navbar-header">
51
+ <a href="/1.13/1.13" class="navbar-brand">
52
+ <img alt="logo" src="/1.13/assets/img/logo.png">
53
+ </a>
54
+ </div>
55
+ <nav class="" role="navigation">
56
+ <ul class="nav navbar-nav nav-links">
57
+ <li>
58
+ <a class="quickstart nav-link" href="/1.13/quickstart">Quickstart</a>
59
+ </li>
60
+ <li>
61
+ <a class="guides nav-link" href="/1.13/guides">Guides</a>
62
+ </li>
63
+ <li>
64
+ <a class="tutorial nav-link" href="/1.13/tutorial">Tutorial</a>
65
+ </li>
66
+ <li>
67
+ <a class="spraypaint nav-link" href="/1.13/js">Spraypaint</a>
68
+ </li>
69
+ </ul>
70
+ <ul class="nav gh navbar-nav navbar-right visible-lg visible-md">
71
+ <li>
72
+ <span class="star">⭐</span>
73
+ <a href="https://github.com/graphiti-api/graphiti">
74
+ <img alt="github-star" style="margin-right: 65px;margin-top: -20px" src="/1.13/assets/img/gh.png">
75
+ </a>
76
+ </li>
77
+ </ul>
78
+ </nav>
79
+ </div>
80
+ </header>
81
+
82
+ <div class="container">
83
+ <div class="toc col-md-3">
84
+ <h1 id="testing">Testing</h1>
85
+
86
+ <ul>
87
+ <li>1 <a href="#overview">Overview</a>
88
+ <ul>
89
+ <li><a href="#api-vs-resource">API vs Resource</a></li>
90
+ <li><a href="#factories">Factories</a></li>
91
+ <li><a href="#rspec">RSpec</a></li>
92
+ </ul>
93
+ </li>
94
+ <li>2 <a href="#test-helpers">Test Helpers</a>
95
+ <ul>
96
+ <li><a href="#jsonapidata"><code class="language-plaintext highlighter-rouge">#jsonapi_data</code></a>
97
+ <ul>
98
+ <li><a href="#accessing-sideloads">Accessing Sideloads</a></li>
99
+ <li><a href="#accessing-links">Accessing Links</a></li>
100
+ </ul>
101
+ </li>
102
+ <li><a href="#json"><code class="language-plaintext highlighter-rouge">json</code></a></li>
103
+ <li><a href="#date-and-datetime"><code class="language-plaintext highlighter-rouge">date/time</code></a></li>
104
+ <li><a href="#jsonapi-errors"><code class="language-plaintext highlighter-rouge">jsonapi_errors</code></a></li>
105
+ <li><a href="#resource-test-helpers">Resource Test Helpers</a></li>
106
+ <li><a href="#api-test-helpers">API Test Helpers</a></li>
107
+ <li><a href="#guard-helpers">Guard Helpers</a></li>
108
+ </ul>
109
+ </li>
110
+ <li>3 <a href="#resource-tests">Resource Tests</a>
111
+ <ul>
112
+ <li><a href="#reads">Reads</a>
113
+ <ul>
114
+ <li><a href="#serialization">Serialization</a></li>
115
+ <li><a href="#filtering">Filtering</a></li>
116
+ <li><a href="#sorting">Sorting</a></li>
117
+ <li><a href="#sideloading">Sideloading</a></li>
118
+ </ul>
119
+ </li>
120
+ <li><a href="#writes">Writes</a>
121
+ <ul>
122
+ <li><a href="#create">Create</a>
123
+ <ul>
124
+ <li><a href="#required-belongs-to">Required belongs_to</a></li>
125
+ </ul>
126
+ </li>
127
+ <li><a href="#update">Update</a></li>
128
+ <li><a href="#destroy">Destroy</a></li>
129
+ <li><a href="#side-effects">Side Effects</a></li>
130
+ </ul>
131
+ </li>
132
+ </ul>
133
+ </li>
134
+ <li>4 <a href="#api-tests">API Tests</a>
135
+ <ul>
136
+ <li><a href="#reads-1">Reads</a>
137
+ <ul>
138
+ <li><a href="#index">Index</a></li>
139
+ <li><a href="#show">Show</a></li>
140
+ </ul>
141
+ </li>
142
+ <li><a href="#writes-1">Writes</a>
143
+ <ul>
144
+ <li><a href="#create-1">Create</a></li>
145
+ <li><a href="#update-1">Update</a></li>
146
+ <li><a href="#destroy-1">Destroy</a></li>
147
+ </ul>
148
+ </li>
149
+ </ul>
150
+ </li>
151
+ <li>5 <a href="#context">Context</a></li>
152
+ <li>6 <a href="#schema-validation">Schema Validation</a></li>
153
+ <li>7 <a href="#testing-spectrum">Testing Spectrum</a></li>
154
+ <li>8 <a href="#double-testing-units">Double-Testing Units</a></li>
155
+ <li>9 <a href="#generators">Generators</a></li>
156
+ </ul>
157
+
158
+ </div>
159
+
160
+ <div class="col-md-8">
161
+
162
+ <a class="anchor" id="overview" />
163
+ <a class="header" href="#overview">
164
+ <h2>
165
+ 1 Overview
166
+ </h2>
167
+ </a>
168
+
169
+ <p>Test first.</p>
170
+
171
+ <p>Wait, hear me out!</p>
172
+
173
+ <p><a href="http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html">Even if you’re not a fan of TDD</a>, Graphiti <em>integration</em> tests are simply the easiest, most pleasant way to develop.
174
+ In fact, most Graphiti development can happen without even opening a browser.
175
+ And as a side effect, you get a reliable test suite.</p>
176
+
177
+ <p>Let’s say we want to filter Employees by <code class="language-plaintext highlighter-rouge">title</code>, which comes from
178
+ the <code class="language-plaintext highlighter-rouge">positions</code> table. Start with a spec:</p>
179
+
180
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">EmployeeResource</span><span class="p">,</span> <span class="ss">type: :resource</span> <span class="k">do</span>
181
+ <span class="n">describe</span> <span class="s1">'filtering'</span> <span class="k">do</span>
182
+ <span class="n">context</span> <span class="s1">'by title'</span> <span class="k">do</span>
183
+ <span class="c1"># GIVEN some seed data</span>
184
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee1</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
185
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee2</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
186
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:position1</span><span class="p">)</span> <span class="k">do</span>
187
+ <span class="n">create</span> <span class="ss">:position</span><span class="p">,</span>
188
+ <span class="ss">title: </span><span class="s1">'foo'</span><span class="p">,</span>
189
+ <span class="ss">employee: </span><span class="n">employee1</span>
190
+ <span class="k">end</span>
191
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:position2</span><span class="p">)</span> <span class="k">do</span>
192
+ <span class="n">create</span> <span class="ss">:position</span><span class="p">,</span>
193
+ <span class="ss">title: </span><span class="s1">'bar'</span><span class="p">,</span>
194
+ <span class="ss">employee: </span><span class="n">employee2</span>
195
+ <span class="k">end</span>
196
+
197
+ <span class="c1"># WHEN a parameter is set</span>
198
+ <span class="n">before</span> <span class="k">do</span>
199
+ <span class="n">params</span><span class="p">[</span><span class="ss">:filter</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">title: </span><span class="s1">'bar'</span> <span class="p">}</span>
200
+ <span class="k">end</span>
201
+
202
+ <span class="c1"># THEN the query results will be correct</span>
203
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
204
+ <span class="n">expect</span><span class="p">(</span><span class="n">records</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="n">employee2</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
205
+ <span class="k">end</span>
206
+ <span class="k">end</span>
207
+ <span class="k">end</span>
208
+ <span class="k">end</span></code></pre></figure>
209
+
210
+ <p>By developing test-first:</p>
211
+
212
+ <ul>
213
+ <li>We don’t need to struggle with seeding local development data or
214
+ finding the right records for specific scenarios - we can
215
+ seed randomized data on-the-fly with <a href="https://github.com/thoughtbot/factory_bot">factories</a>.</li>
216
+ <li>There’s no need to spin up a server and refresh browser pages,
217
+ mentally parsing the response payload.</li>
218
+ <li>We get a high-confidence test “for free”.</li>
219
+ <li>Because our integration test is separate from implementation, we don’t
220
+ need to worry about <a href="http://david.heinemeierhansson.com/2014/test-induced-design-damage.html">test-induced design damage</a>.</li>
221
+ </ul>
222
+
223
+ <a class="anchor" id="api-vs-resource" />
224
+ <a class="header" href="#api-vs-resource">
225
+ <h3>
226
+ 1.1 API vs Resource
227
+ </h3>
228
+ </a>
229
+
230
+ <p>There are two types of Graphiti tests: <strong>API tests</strong> and <strong>Resource
231
+ tests</strong>.</p>
232
+
233
+ <p>This is because the same Resource logic can be re-used at multiple
234
+ endpoints. PostResource can be referenced at <code class="language-plaintext highlighter-rouge">/posts</code>, <code class="language-plaintext highlighter-rouge">/top_posts</code>,
235
+ and <code class="language-plaintext highlighter-rouge">/admin/posts</code>, but we shouldn’t have to test the same filtering and
236
+ sorting logic over and over. Querying, persistence, and serialization are
237
+ all Resource responsibilities, tested in Resource tests.</p>
238
+
239
+ <p>We still want API tests, though, to test everything outside of the
240
+ Resource: routing, middleware, cache rules, response codes, etc…</p>
241
+
242
+ <p>Typically, you’ll write the API test <strong>once</strong> and not have to touch it
243
+ again.</p>
244
+
245
+ <a class="anchor" id="factories" />
246
+ <a class="header" href="#factories">
247
+ <h3>
248
+ 1.2 Factories
249
+ </h3>
250
+ </a>
251
+
252
+ <blockquote>
253
+ <p>Note: Factories are not <strong>required</strong>, but they are considered a best
254
+ practice used by the Graphiti test generator. Read thoughtbot’s
255
+ <a href="https://robots.thoughtbot.com/why-factories">Why Factories?</a> for more
256
+ information.</p>
257
+ </blockquote>
258
+
259
+ <p>We need to seed data into our test database. To do this, we use <a href="https://github.com/thoughtbot/factory_bot">Factory
260
+ Bot</a> and <a href="https://github.com/stympy/faker">Faker</a>.</p>
261
+
262
+ <p>When you generate a model, a stub factory will be created. It is highly
263
+ recommended you edit that factory with randomized data:</p>
264
+
265
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># BEFORE</span>
266
+ <span class="no">FactoryBot</span><span class="p">.</span><span class="nf">define</span> <span class="k">do</span>
267
+ <span class="n">factory</span> <span class="ss">:employee</span> <span class="k">do</span>
268
+ <span class="n">first_name</span> <span class="p">{</span> <span class="s1">'MyString'</span> <span class="p">}</span>
269
+ <span class="k">end</span>
270
+ <span class="k">end</span>
271
+
272
+ <span class="c1"># AFTER</span>
273
+ <span class="no">FactoryBot</span><span class="p">.</span><span class="nf">define</span> <span class="k">do</span>
274
+ <span class="n">factory</span> <span class="ss">:employee</span> <span class="k">do</span>
275
+ <span class="n">first_name</span> <span class="p">{</span> <span class="no">Faker</span><span class="o">::</span><span class="no">Name</span><span class="p">.</span><span class="nf">first_name</span> <span class="p">}</span>
276
+ <span class="k">end</span>
277
+ <span class="k">end</span></code></pre></figure>
278
+
279
+ <p>This will help catch edge cases and provide more clarity than seeing the
280
+ same <code class="language-plaintext highlighter-rouge">"MyString"</code> everywhere.</p>
281
+
282
+ <p>It’s a best practice that if a factory defines an attribute, there
283
+ should be a corresponding validation around that attribute. If an
284
+ attribute is optional, it should not be defaulted in a factory.</p>
285
+
286
+ <p>Finally, <a href="https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html">Rails 5 made belongs_to required by default</a>. This means that if Employee <code class="language-plaintext highlighter-rouge">belongs_to :department</code>, then <code class="language-plaintext highlighter-rouge">create(:employee)</code> will fail. To ensure a relationship is always seeded:</p>
287
+
288
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">FactoryBot</span><span class="p">.</span><span class="nf">define</span> <span class="k">do</span>
289
+ <span class="n">factory</span> <span class="ss">:employee</span> <span class="k">do</span>
290
+ <span class="n">department</span>
291
+ <span class="c1"># OR association :department, factory: :department</span>
292
+ <span class="k">end</span>
293
+ <span class="k">end</span></code></pre></figure>
294
+
295
+ <a class="anchor" id="rspec" />
296
+ <a class="header" href="#rspec">
297
+ <h3>
298
+ 1.3 RSpec
299
+ </h3>
300
+ </a>
301
+
302
+ <p>RSpec is not <strong>required</strong>, but considered a first-class citizen used by the
303
+ Graphiti test generator.</p>
304
+
305
+ <a class="anchor" id="test-helpers" />
306
+ <a class="header" href="#test-helpers">
307
+ <h2>
308
+ 2 Test Helpers
309
+ </h2>
310
+ </a>
311
+
312
+ <p>Tests are run using <a href="http://jsonapi.org/format/#fetching-includes">JSONAPI standards</a>. But the
313
+ JSONAPI payload can be a pain to deal with. So, we’ve supplied helpers.</p>
314
+
315
+ <p>These helpers ship with Graphiti, under <code class="language-plaintext highlighter-rouge">Graphiti::SpecHelpers</code>.</p>
316
+
317
+ <a class="anchor" id="jsonapi-data" />
318
+ <a class="header" href="#jsonapi-data">
319
+ <h3>
320
+ 2.1 #jsonapi_data
321
+ </h3>
322
+ </a>
323
+
324
+ <blockquote>
325
+ <p>Note: for brevity, this method is aliased to <code class="language-plaintext highlighter-rouge">d</code></p>
326
+ </blockquote>
327
+
328
+ <p>The <code class="language-plaintext highlighter-rouge">jsonapi_data</code> method will parse response data and return a
329
+ normalized object (<code class="language-plaintext highlighter-rouge">Graphiti::SpecHelpers::Node</code>). Assert against this the same way you assert against
330
+ JSON:</p>
331
+
332
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">data</span> <span class="o">=</span> <span class="n">jsonapi_data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
333
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">id</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
334
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">jsonapi_type</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'employees'</span><span class="p">)</span>
335
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Jane'</span><span class="p">)</span></code></pre></figure>
336
+
337
+ <ul>
338
+ <li><code class="language-plaintext highlighter-rouge">id</code> will automatically case to an integer. If you would like to avoid
339
+ this, use <code class="language-plaintext highlighter-rouge">rawid</code> instead.</li>
340
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_type</code> is a convenience method for <code class="language-plaintext highlighter-rouge">data/type</code>, to avoid
341
+ conflicting with an attribute of the same name.</li>
342
+ <li>If the <code class="language-plaintext highlighter-rouge">first_name</code> key was not present in the response, an error will
343
+ be raised.</li>
344
+ </ul>
345
+
346
+ <a class="anchor" id="accessing-sideloads" />
347
+ <a class="header" href="#accessing-sideloads">
348
+ <h4>
349
+ 2.2 Accessing Sideloads
350
+ </h4>
351
+ </a>
352
+
353
+ <p>To grab a relationship:</p>
354
+
355
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">sideload</span> <span class="o">=</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">sideload</span><span class="p">(</span><span class="ss">:comments</span><span class="p">)</span>
356
+ <span class="n">expect</span><span class="p">(</span><span class="n">sideload</span><span class="p">.</span><span class="nf">id</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">123</span><span class="p">)</span>
357
+ <span class="n">expect</span><span class="p">(</span><span class="n">sideload</span><span class="p">.</span><span class="nf">jsonapi_type</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'comments'</span><span class="p">)</span>
358
+ <span class="n">expect</span><span class="p">(</span><span class="n">sideload</span><span class="p">.</span><span class="nf">body</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'body'</span><span class="p">)</span></code></pre></figure>
359
+
360
+ <p>The <code class="language-plaintext highlighter-rouge">sideload</code> method accepts the <em>name of the relationship</em>. It returns
361
+ a normal <code class="language-plaintext highlighter-rouge">jsonapi_data</code> <code class="language-plaintext highlighter-rouge">Graphiti::SpecHelpers::Node</code> containing the <code class="language-plaintext highlighter-rouge">include</code>-ed data.</p>
362
+
363
+ <a class="anchor" id="accessing-links" />
364
+ <a class="header" href="#accessing-links">
365
+ <h4>
366
+ 2.3 Accessing Links
367
+ </h4>
368
+ </a>
369
+
370
+ <p>To grab a Link:</p>
371
+
372
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">link</span><span class="p">(</span><span class="ss">:comments</span><span class="p">,</span> <span class="ss">:related</span><span class="p">)</span></code></pre></figure>
373
+
374
+ <p>This accepts the relationship name and the link type. It will return the
375
+ link URL.</p>
376
+
377
+ <a class="anchor" id="json" />
378
+ <a class="header" href="#json">
379
+ <h3>
380
+ 2.2 #json
381
+ </h3>
382
+ </a>
383
+
384
+ <p>To see the raw JSON response, use <code class="language-plaintext highlighter-rouge">json</code>.</p>
385
+
386
+ <a class="anchor" id="date-and-datetime" />
387
+ <a class="header" href="#date-and-datetime">
388
+ <h3>
389
+ 2.3 #date and #datetime
390
+ </h3>
391
+ </a>
392
+
393
+ <p>In Graphiti, datetimes are rendered in <a href="https://www.iso.org/iso-8601-date-and-time-format.html">ISO 8601 format</a>. This means that straight date comparisons will fail:</p>
394
+
395
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># WRONG</span>
396
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">created_at</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">post</span><span class="p">.</span><span class="nf">created_at</span><span class="p">)</span></code></pre></figure>
397
+
398
+ <p>Instead, use the <code class="language-plaintext highlighter-rouge">datetime</code> helper to convert to ISO 8601 and compare
399
+ apples to apples:</p>
400
+
401
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># RIGHT</span>
402
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">created_at</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="n">post</span><span class="p">.</span><span class="nf">created_at</span><span class="p">))</span></code></pre></figure>
403
+
404
+ <p>Similarly, there’s a <code class="language-plaintext highlighter-rouge">date</code> helper as well.</p>
405
+
406
+ <a class="anchor" id="jsonapi-errors" />
407
+ <a class="header" href="#jsonapi-errors">
408
+ <h3>
409
+ 2.4 #jsonapi_errors
410
+ </h3>
411
+ </a>
412
+
413
+ <blockquote>
414
+ <p>This method is aliased to <code class="language-plaintext highlighter-rouge">errors</code> for brevity</p>
415
+ </blockquote>
416
+
417
+ <p>To parse an <a href="http://jsonapi.org/format/#errors">Errors Payload</a>:</p>
418
+
419
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">errors</span> <span class="o">=</span> <span class="n">jsonapi_errors</span>
420
+
421
+ <span class="c1"># Direct access</span>
422
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">.</span><span class="nf">length</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
423
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">attribute</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>
424
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'422'</span><span class="p">)</span>
425
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">title</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Validation Error'</span><span class="p">)</span>
426
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">detail</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s2">"Name can't be blank"</span><span class="p">)</span>
427
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">code</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="ss">:blank</span><span class="p">)</span>
428
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">message</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s2">"can't be blank"</span><span class="p">)</span>
429
+
430
+ <span class="c1"># By attribute</span>
431
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">message</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s2">"can't be blank"</span><span class="p">)</span>
432
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">code</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="ss">:blank</span><span class="p">)</span>
433
+ <span class="c1"># ... etc ...</span>
434
+
435
+ <span class="c1"># As a hash</span>
436
+ <span class="n">expect</span><span class="p">(</span><span class="n">errors</span><span class="p">.</span><span class="nf">to_h</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">({</span>
437
+ <span class="ss">name: </span><span class="s2">"can't be blank"</span>
438
+ <span class="p">})</span></code></pre></figure>
439
+
440
+ <a class="anchor" id="resource-test-helpers" />
441
+ <a class="header" href="#resource-test-helpers">
442
+ <h3>
443
+ 2.5 Resource Test Helpers
444
+ </h3>
445
+ </a>
446
+
447
+ <p>Resource tests have two helpers, both different ways to execute a query.</p>
448
+
449
+ <p><code class="language-plaintext highlighter-rouge">render</code> will fire the query and return a JSON response that can be
450
+ accessed as normal:</p>
451
+
452
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
453
+ <span class="n">render</span>
454
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Jane'</span><span class="p">)</span>
455
+ <span class="n">json</span> <span class="c1"># =&gt; { data: { type: 'employees', ... } }</span>
456
+ <span class="k">end</span></code></pre></figure>
457
+
458
+ <p><code class="language-plaintext highlighter-rouge">records</code> will return model instances:</p>
459
+
460
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
461
+ <span class="n">render</span>
462
+ <span class="n">expect</span><span class="p">(</span><span class="n">records</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
463
+ <span class="k">end</span></code></pre></figure>
464
+
465
+ <a class="anchor" id="api-test-helpers" />
466
+ <a class="header" href="#api-test-helpers">
467
+ <h3>
468
+ 2.6 API Test Helpers
469
+ </h3>
470
+ </a>
471
+
472
+ <p>When executing an API test request, always use the <code class="language-plaintext highlighter-rouge">jsonapi_</code> doppelgänger:</p>
473
+
474
+ <ul>
475
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_get(url, params:)</code> instead of <code class="language-plaintext highlighter-rouge">get</code></li>
476
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_post(url, payload)</code> instead of <code class="language-plaintext highlighter-rouge">post</code></li>
477
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_put(url, payload)</code> instead of <code class="language-plaintext highlighter-rouge">put</code></li>
478
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_patch(url, payload)</code> instead of <code class="language-plaintext highlighter-rouge">patch</code></li>
479
+ <li><code class="language-plaintext highlighter-rouge">jsonapi_delete(url)</code> instead of <code class="language-plaintext highlighter-rouge">delete</code></li>
480
+ </ul>
481
+
482
+ <p>This will set the <code class="language-plaintext highlighter-rouge">CONTENT_TYPE</code> header to <code class="language-plaintext highlighter-rouge">application/vnd.api+json</code>
483
+ and call <code class="language-plaintext highlighter-rouge">to_json</code> on the payload (when applicable).</p>
484
+
485
+ <p>It also allows overriding <code class="language-plaintext highlighter-rouge">jsonapi_headers</code>. Use this to manipulate
486
+ headers for a given request:</p>
487
+
488
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">jsonapi_headers</span>
489
+ <span class="k">super</span><span class="p">.</span><span class="nf">tap</span> <span class="k">do</span> <span class="o">|</span><span class="n">headers</span><span class="o">|</span>
490
+ <span class="n">headers</span><span class="p">[</span><span class="s1">'CUSTOM'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'foo'</span>
491
+ <span class="k">end</span>
492
+ <span class="k">end</span></code></pre></figure>
493
+
494
+ <a class="anchor" id="guard-helpers" />
495
+ <a class="header" href="#guard-helpers">
496
+ <h3>
497
+ 2.7 Guard Helpers
498
+ </h3>
499
+ </a>
500
+
501
+ <p>Many teams use <a href="https://github.com/guard/guard">guard</a> in development to watch
502
+ their project files and run a smaller set of focused tests as code changes. For
503
+ those teams leveraging guard and the <a href="https://github.com/guard/guard-rspec">guard-rspec plugin</a>,
504
+ we offer an additional set of DSL helpers via the
505
+ <a href="https://github.com/graphiti-api/guard-rspec-graphiti">guard-rspec-graphiti plugin</a>.
506
+ For more details, check out the <a href="https://github.com/graphiti-api/guard-rspec-graphiti/blob/master/README.md">project README</a>.</p>
507
+
508
+ <a class="anchor" id="resource-tests" />
509
+ <a class="header" href="#resource-tests">
510
+ <h2>
511
+ 3 Resource Tests
512
+ </h2>
513
+ </a>
514
+
515
+ <p>There are two test files for each Resource:</p>
516
+
517
+ <ul>
518
+ <li><code class="language-plaintext highlighter-rouge">spec/resources/post/reads_spec.rb</code></li>
519
+ <li><code class="language-plaintext highlighter-rouge">spec/resources/post/writes_spec.rb</code></li>
520
+ </ul>
521
+
522
+ <a class="anchor" id="reads" />
523
+ <a class="header" href="#reads">
524
+ <h3>
525
+ 3.1 Reads
526
+ </h3>
527
+ </a>
528
+
529
+ <p>The basic setup for read operations:</p>
530
+
531
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># spec/resources/employee/reads_spec.rb</span>
532
+ <span class="nb">require</span> <span class="s1">'rails_helper'</span>
533
+
534
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">EmployeeResource</span><span class="p">,</span> <span class="ss">type: :resource</span> <span class="k">do</span>
535
+ <span class="n">describe</span> <span class="s1">'serialization'</span> <span class="k">do</span>
536
+ <span class="c1"># ... code ...</span>
537
+ <span class="k">end</span>
538
+
539
+ <span class="n">describe</span> <span class="s1">'filtering'</span> <span class="k">do</span>
540
+ <span class="c1"># ... code ...</span>
541
+ <span class="k">end</span>
542
+
543
+ <span class="n">describe</span> <span class="s1">'sorting'</span> <span class="k">do</span>
544
+ <span class="c1"># ... code ...</span>
545
+ <span class="k">end</span>
546
+
547
+ <span class="n">describe</span> <span class="s1">'sideloading'</span> <span class="k">do</span>
548
+ <span class="c1"># ... code ...</span>
549
+ <span class="k">end</span>
550
+ <span class="k">end</span></code></pre></figure>
551
+
552
+ <a class="anchor" id="serialization" />
553
+ <a class="header" href="#serialization">
554
+ <h4>
555
+ 3.1.1 Serialization
556
+ </h4>
557
+ </a>
558
+
559
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'serialization'</span> <span class="k">do</span>
560
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">,</span> <span class="ss">first_name: </span><span class="s1">'Jane'</span><span class="p">)</span> <span class="p">}</span>
561
+
562
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
563
+ <span class="n">render</span>
564
+ <span class="n">data</span> <span class="o">=</span> <span class="n">jsonapi_data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
565
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">id</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
566
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">jsonapi_type</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'employees'</span><span class="p">)</span>
567
+ <span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Jane'</span><span class="p">)</span>
568
+ <span class="k">end</span>
569
+ <span class="k">end</span></code></pre></figure>
570
+
571
+ <p>We want to test that our attributes render correctly. We’ll do this by
572
+ seeding a record, firing a basic query, and comparing the JSON result to
573
+ the seeded data.</p>
574
+
575
+ <p>Best practices:</p>
576
+
577
+ <ul>
578
+ <li>Assert on all attributes, even if there is no logic. This way adding
579
+ logic will cause a test failure.</li>
580
+ <li>When seeding data, manually assign values. This way you can be assured
581
+ you aren’t accidentally testing <code class="language-plaintext highlighter-rouge">nil == nil</code></li>
582
+ </ul>
583
+
584
+ <p>If you decide you have a high level of confidence in your factories, you
585
+ can instead save some keystrokes and assert on randomized data:</p>
586
+
587
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">expect</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">first_name</span><span class="p">)</span></code></pre></figure>
588
+
589
+ <blockquote>
590
+ <p>Note: Our schema validation test will ensure no attributes get
591
+ removed or change types.</p>
592
+ </blockquote>
593
+
594
+ <a class="anchor" id="filtering" />
595
+ <a class="header" href="#filtering">
596
+ <h4>
597
+ 3.1.2 Filtering
598
+ </h4>
599
+ </a>
600
+
601
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'filtering'</span> <span class="k">do</span>
602
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee1</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
603
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee2</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
604
+
605
+ <span class="n">context</span> <span class="s1">'by id'</span> <span class="k">do</span>
606
+ <span class="n">before</span> <span class="k">do</span>
607
+ <span class="n">params</span><span class="p">[</span><span class="ss">:filter</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">id: </span><span class="p">{</span> <span class="ss">eq: </span><span class="n">employee2</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span> <span class="p">}</span>
608
+ <span class="k">end</span>
609
+
610
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
611
+ <span class="n">render</span>
612
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="n">employee2</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
613
+ <span class="k">end</span>
614
+ <span class="k">end</span>
615
+ <span class="k">end</span></code></pre></figure>
616
+
617
+ <p>Here we seed data, set the filter parameter, and assert only records
618
+ matching the given criteria are present in the response.</p>
619
+
620
+ <p>In general, you only need to test filtering when there is custom logic.
621
+ Our schema validation test will ensure no filters are removed, guarded,
622
+ changed operators, etc.</p>
623
+
624
+ <a class="anchor" id="sorting" />
625
+ <a class="header" href="#sorting">
626
+ <h4>
627
+ 3.1.3 Sorting
628
+ </h4>
629
+ </a>
630
+
631
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'sorting'</span> <span class="k">do</span>
632
+ <span class="n">describe</span> <span class="s1">'by id'</span> <span class="k">do</span>
633
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee1</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
634
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee2</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
635
+
636
+ <span class="n">context</span> <span class="s1">'when ascending'</span> <span class="k">do</span>
637
+ <span class="n">before</span> <span class="k">do</span>
638
+ <span class="n">params</span><span class="p">[</span><span class="ss">:sort</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'id'</span>
639
+ <span class="k">end</span>
640
+
641
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
642
+ <span class="n">render</span>
643
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span>
644
+ <span class="n">employee1</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span>
645
+ <span class="n">employee2</span><span class="p">.</span><span class="nf">id</span>
646
+ <span class="p">])</span>
647
+ <span class="k">end</span>
648
+ <span class="k">end</span>
649
+
650
+ <span class="n">context</span> <span class="s1">'when descending'</span> <span class="k">do</span>
651
+ <span class="n">before</span> <span class="k">do</span>
652
+ <span class="n">params</span><span class="p">[</span><span class="ss">:sort</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'-id'</span>
653
+ <span class="k">end</span>
654
+
655
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
656
+ <span class="n">render</span>
657
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span>
658
+ <span class="n">employee2</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span>
659
+ <span class="n">employee1</span><span class="p">.</span><span class="nf">id</span>
660
+ <span class="p">])</span>
661
+ <span class="k">end</span>
662
+ <span class="k">end</span>
663
+ <span class="k">end</span>
664
+ <span class="k">end</span></code></pre></figure>
665
+
666
+ <p>Here we seed data, set the sort parameter, and assert the correct order
667
+ of the rendered response.</p>
668
+
669
+ <p>In general, you only need to test sorting when there is custom logic.
670
+ Our schema validation test will ensure no sorts are removed, guarded or
671
+ limited in direction.</p>
672
+
673
+ <a class="anchor" id="sideloading" />
674
+ <a class="header" href="#sideloading">
675
+ <h4>
676
+ 3.1.4 Sideloading
677
+ </h4>
678
+ </a>
679
+
680
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'sideloading'</span> <span class="k">do</span>
681
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
682
+
683
+ <span class="n">describe</span> <span class="s1">'current_position'</span> <span class="k">do</span>
684
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:pos1</span><span class="p">)</span> <span class="k">do</span>
685
+ <span class="n">create</span><span class="p">(</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">employee: </span><span class="n">employee</span><span class="p">,</span> <span class="ss">historical_index: </span><span class="mi">2</span><span class="p">)</span>
686
+ <span class="k">end</span>
687
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:pos2</span><span class="p">)</span> <span class="k">do</span>
688
+ <span class="n">create</span><span class="p">(</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">employee: </span><span class="n">employee</span><span class="p">,</span> <span class="ss">historical_index: </span><span class="mi">1</span><span class="p">)</span>
689
+ <span class="k">end</span>
690
+
691
+ <span class="n">before</span> <span class="k">do</span>
692
+ <span class="n">params</span><span class="p">[</span><span class="ss">:include</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'current_position'</span>
693
+ <span class="k">end</span>
694
+
695
+ <span class="n">it</span> <span class="s1">'returns position with historical index == 1'</span> <span class="k">do</span>
696
+ <span class="n">render</span>
697
+ <span class="n">sl</span> <span class="o">=</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">sideload</span><span class="p">(</span><span class="ss">:current_position</span><span class="p">)</span>
698
+ <span class="n">expect</span><span class="p">(</span><span class="n">sl</span><span class="p">.</span><span class="nf">jsonapi_type</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'positions'</span><span class="p">)</span>
699
+ <span class="n">expect</span><span class="p">(</span><span class="n">sl</span><span class="p">.</span><span class="nf">id</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">pos2</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
700
+ <span class="k">end</span>
701
+ <span class="k">end</span>
702
+ <span class="k">end</span></code></pre></figure>
703
+
704
+ <p>Here we seed data, set the sideload parameter, and assert the correct
705
+ entity is present in the request. There is no need to test each
706
+ attribute of the sideload - this should be tested in the <a href="#resource-tests">Resource
707
+ Test</a> of the sideloaded Resource.</p>
708
+
709
+ <p>In general, you only need to test sideloads when there is custom logic.
710
+ Our schema validation test will ensure no sideloads are removed or
711
+ associated to a different Resource.</p>
712
+
713
+ <a class="anchor" id="writes" />
714
+ <a class="header" href="#writes">
715
+ <h3>
716
+ 3.2 Writes
717
+ </h3>
718
+ </a>
719
+
720
+ <p>The basic setup for write operations:</p>
721
+
722
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># spec/resources/employee/writes_spec.rb</span>
723
+ <span class="nb">require</span> <span class="s1">'rails_helper'</span>
724
+
725
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">EmployeeResource</span><span class="p">,</span> <span class="ss">type: :resource</span> <span class="k">do</span>
726
+ <span class="n">describe</span> <span class="s1">'creating'</span> <span class="k">do</span>
727
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
728
+ <span class="c1"># ... code ...</span>
729
+ <span class="k">end</span>
730
+
731
+ <span class="n">describe</span> <span class="s1">'creating'</span> <span class="k">do</span>
732
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
733
+ <span class="c1"># ... code ...</span>
734
+ <span class="k">end</span>
735
+
736
+ <span class="n">describe</span> <span class="s1">'destroying'</span> <span class="k">do</span>
737
+ <span class="c1"># ... code ...</span>
738
+ <span class="k">end</span>
739
+ <span class="k">end</span></code></pre></figure>
740
+
741
+ <p>Here <code class="language-plaintext highlighter-rouge">payload</code> is a <a href="http://jsonapi.org/format/#crud">JSONAPI Resource Object</a>.</p>
742
+
743
+ <a class="anchor" id="create" />
744
+ <a class="header" href="#create">
745
+ <h4>
746
+ 3.2.1 Create
747
+ </h4>
748
+ </a>
749
+
750
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'creating'</span> <span class="k">do</span>
751
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
752
+ <span class="p">{</span>
753
+ <span class="ss">data: </span><span class="p">{</span>
754
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
755
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="p">}</span>
756
+ <span class="p">}</span>
757
+ <span class="p">}</span>
758
+ <span class="k">end</span><span class="p">)</span>
759
+
760
+ <span class="n">let</span><span class="p">(</span><span class="ss">:instance</span><span class="p">)</span> <span class="k">do</span>
761
+ <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">build</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
762
+ <span class="k">end</span>
763
+
764
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
765
+ <span class="n">expect</span> <span class="p">{</span>
766
+ <span class="n">expect</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="nf">save</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span>
767
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
768
+ <span class="k">end</span>
769
+ <span class="k">end</span></code></pre></figure>
770
+
771
+ <p>Here <code class="language-plaintext highlighter-rouge">payload</code> is an empty Employee <a href="http://jsonapi.org/format/#crud">Resource Object</a>.
772
+ We’ll assert that when saving this empty payload, an Employee is
773
+ created.</p>
774
+
775
+ <p>You’ll likely want to add attributes here and ensure they are persisted
776
+ correctly:</p>
777
+
778
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
779
+ <span class="p">{</span>
780
+ <span class="ss">data: </span><span class="p">{</span>
781
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
782
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">first_name: </span><span class="s1">'Jane'</span><span class="p">,</span> <span class="ss">age: </span><span class="mi">30</span> <span class="p">}</span>
783
+ <span class="p">}</span>
784
+ <span class="p">}</span>
785
+ <span class="k">end</span>
786
+
787
+ <span class="c1"># ... code ...</span>
788
+
789
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
790
+ <span class="n">expect</span> <span class="p">{</span>
791
+ <span class="n">expect</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="nf">save</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span>
792
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
793
+ <span class="n">employee</span> <span class="o">=</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">last</span>
794
+ <span class="n">expect</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Jane'</span><span class="p">)</span>
795
+ <span class="n">expect</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">age</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span>
796
+ <span class="k">end</span></code></pre></figure>
797
+
798
+ <a class="anchor" id="required-belongs-to" />
799
+ <a class="header" href="#required-belongs-to">
800
+ <h5>
801
+ 3.2.1.1 Required Belongs To
802
+ </h5>
803
+ </a>
804
+
805
+ <p><a href="https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html">Rails 5 made belongs_to required by default</a>. This means that if Employee <code class="language-plaintext highlighter-rouge">belongs_to :department</code>, the above tests will fail (we cannot create the Employee without associating it to Department).</p>
806
+
807
+ <p>You have 3 options here:</p>
808
+
809
+ <ul>
810
+ <li>Turn off this validation in test mode. Add <code class="language-plaintext highlighter-rouge">config.active_record.belongs_to_required_by_default = false</code> to <code class="language-plaintext highlighter-rouge">config/environments/test.rb</code>.</li>
811
+ <li>Turn off the validation for this specific relationship: <code class="language-plaintext highlighter-rouge">belongs_to :department, optional: true</code>.</li>
812
+ <li>Associate as part of the request.</li>
813
+ </ul>
814
+
815
+ <p>We recommend the third option to preserve real-world end-to-end
816
+ behavior:</p>
817
+
818
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'creating'</span> <span class="k">do</span>
819
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:department</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:department</span><span class="p">)</span> <span class="p">}</span>
820
+
821
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
822
+ <span class="p">{</span>
823
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
824
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="o">...</span> <span class="p">},</span>
825
+ <span class="ss">relationships: </span><span class="p">{</span>
826
+ <span class="ss">department: </span><span class="p">{</span>
827
+ <span class="ss">data: </span><span class="p">{</span>
828
+ <span class="ss">type: </span><span class="s1">'departments'</span><span class="p">,</span>
829
+ <span class="ss">id: </span><span class="n">department</span><span class="p">.</span><span class="nf">id</span><span class="p">.</span><span class="nf">to_s</span>
830
+ <span class="p">}</span>
831
+ <span class="p">}</span>
832
+ <span class="p">}</span>
833
+ <span class="p">}</span>
834
+ <span class="k">end</span>
835
+
836
+ <span class="c1"># ... code ...</span>
837
+ <span class="k">end</span></code></pre></figure>
838
+
839
+ <p>Will ensure the Employee is created and associated to the given
840
+ department.</p>
841
+
842
+ <a class="anchor" id="update" />
843
+ <a class="header" href="#update">
844
+ <h4>
845
+ 3.2.2 Update
846
+ </h4>
847
+ </a>
848
+
849
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'updating'</span> <span class="k">do</span>
850
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
851
+
852
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
853
+ <span class="p">{</span>
854
+ <span class="ss">data: </span><span class="p">{</span>
855
+ <span class="ss">id: </span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">.</span><span class="nf">to_s</span><span class="p">,</span>
856
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
857
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">first_name: </span><span class="s1">'changed!'</span> <span class="p">}</span>
858
+ <span class="p">}</span>
859
+ <span class="p">}</span>
860
+ <span class="k">end</span>
861
+
862
+ <span class="n">let</span><span class="p">(</span><span class="ss">:instance</span><span class="p">)</span> <span class="k">do</span>
863
+ <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
864
+ <span class="k">end</span>
865
+
866
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
867
+ <span class="n">expect</span> <span class="p">{</span>
868
+ <span class="n">expect</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="nf">update_attributes</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span>
869
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="n">employee</span><span class="p">.</span><span class="nf">reload</span><span class="p">.</span><span class="nf">updated_at</span> <span class="p">}</span>
870
+ <span class="p">.</span><span class="nf">and</span> <span class="n">change</span> <span class="p">{</span> <span class="n">employee</span><span class="p">.</span><span class="nf">first_name</span> <span class="p">}.</span><span class="nf">to</span><span class="p">(</span><span class="s1">'changed!'</span><span class="p">)</span>
871
+ <span class="k">end</span>
872
+ <span class="k">end</span></code></pre></figure>
873
+
874
+ <blockquote>
875
+ <p>Note that this test will be pending by default when using the
876
+ generator, as we require the attributes to be explicitly defined.</p>
877
+ </blockquote>
878
+
879
+ <p>Here <code class="language-plaintext highlighter-rouge">payload</code> is an empty Employee <a href="http://jsonapi.org/format/#crud">Resource Object</a>.
880
+ We’ll assert that when updating attributes, the changes are correctly
881
+ persisted to the database.</p>
882
+
883
+ <a class="anchor" id="destroy" />
884
+ <a class="header" href="#destroy">
885
+ <h4>
886
+ 3.2.3 Destroy
887
+ </h4>
888
+ </a>
889
+
890
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'destroying'</span> <span class="k">do</span>
891
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
892
+
893
+ <span class="n">let</span><span class="p">(</span><span class="ss">:instance</span><span class="p">)</span> <span class="k">do</span>
894
+ <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="ss">id: </span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
895
+ <span class="k">end</span>
896
+
897
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
898
+ <span class="n">expect</span> <span class="p">{</span>
899
+ <span class="n">expect</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="nf">destroy</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span>
900
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
901
+ <span class="k">end</span>
902
+ <span class="k">end</span></code></pre></figure>
903
+
904
+ <p>Here we ensure that a delete request correctly removes a record from the
905
+ database.</p>
906
+
907
+ <a class="anchor" id="side-effects" />
908
+ <a class="header" href="#side-effects">
909
+ <h4>
910
+ 3.2.4 Side Effects
911
+ </h4>
912
+ </a>
913
+
914
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
915
+ <span class="c1"># some assertion</span>
916
+ <span class="n">email</span> <span class="o">=</span> <span class="no">ActionMailer</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">deliveries</span><span class="p">.</span><span class="nf">last</span>
917
+ <span class="n">expect</span><span class="p">(</span><span class="n">email</span><span class="p">.</span><span class="nf">subject</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'Welcome!'</span><span class="p">)</span>
918
+ <span class="k">end</span></code></pre></figure>
919
+
920
+ <p>It’s common for write operations to cause side-effects, such as sending
921
+ an email or updating an audit trail. It’s recommended to test these
922
+ <em>within the same “it” block</em> unless the logic gets particularly intense.
923
+ Though “one expectation per test” works well for unit tests, integration
924
+ tests can take longer to run and the performance penalty isn’t worth it.</p>
925
+
926
+ <a class="anchor" id="api-tests" />
927
+ <a class="header" href="#api-tests">
928
+ <h2>
929
+ 4 API Tests
930
+ </h2>
931
+ </a>
932
+
933
+ <p>There are five test files for each Resource:</p>
934
+
935
+ <ul>
936
+ <li><code class="language-plaintext highlighter-rouge">spec/api/v1/employees/index_spec.rb</code></li>
937
+ <li><code class="language-plaintext highlighter-rouge">spec/api/v1/employees/show_spec.rb</code></li>
938
+ <li><code class="language-plaintext highlighter-rouge">spec/api/v1/employees/create_spec.rb</code></li>
939
+ <li><code class="language-plaintext highlighter-rouge">spec/api/v1/employees/update_spec.rb</code></li>
940
+ <li><code class="language-plaintext highlighter-rouge">spec/api/v1/employees/destroy_spec.rb</code></li>
941
+ </ul>
942
+
943
+ <a class="anchor" id="reads" />
944
+ <a class="header" href="#reads">
945
+ <h3>
946
+ 4.1 Reads
947
+ </h3>
948
+ </a>
949
+
950
+ <a class="anchor" id="index" />
951
+ <a class="header" href="#index">
952
+ <h4>
953
+ 4.1.1 #index
954
+ </h4>
955
+ </a>
956
+
957
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'rails_helper'</span>
958
+
959
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="s2">"employees#index"</span><span class="p">,</span> <span class="ss">type: :request</span> <span class="k">do</span>
960
+ <span class="n">let</span><span class="p">(</span><span class="ss">:params</span><span class="p">)</span> <span class="p">{</span> <span class="p">{}</span> <span class="p">}</span>
961
+
962
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
963
+ <span class="n">jsonapi_get</span> <span class="s2">"/api/v1/employees"</span><span class="p">,</span> <span class="ss">params: </span><span class="n">params</span>
964
+ <span class="k">end</span>
965
+
966
+ <span class="n">describe</span> <span class="s1">'basic fetch'</span> <span class="k">do</span>
967
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee1</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
968
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee2</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
969
+
970
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
971
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:all</span><span class="p">).</span><span class="nf">and_call_original</span>
972
+ <span class="n">make_request</span>
973
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
974
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:jsonapi_type</span><span class="p">).</span><span class="nf">uniq</span><span class="p">)</span>
975
+ <span class="p">.</span><span class="nf">to</span> <span class="n">match_array</span><span class="p">([</span><span class="s1">'employees'</span><span class="p">])</span>
976
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">))</span>
977
+ <span class="p">.</span><span class="nf">to</span> <span class="n">match_array</span><span class="p">([</span><span class="n">employee1</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span> <span class="n">employee2</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
978
+ <span class="k">end</span>
979
+ <span class="k">end</span>
980
+ <span class="k">end</span></code></pre></figure>
981
+
982
+ <p>Here we’re ensuring <code class="language-plaintext highlighter-rouge">EmployeeResource</code> is the correct resource to be
983
+ called from this endpoint, we get a 200 status code, and the entities
984
+ returned are expected.</p>
985
+
986
+ <a class="anchor" id="show" />
987
+ <a class="header" href="#show">
988
+ <h4>
989
+ 4.1.2 #show
990
+ </h4>
991
+ </a>
992
+
993
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'rails_helper'</span>
994
+
995
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="s2">"employees#show"</span><span class="p">,</span> <span class="ss">type: :request</span> <span class="k">do</span>
996
+ <span class="n">let</span><span class="p">(</span><span class="ss">:params</span><span class="p">)</span> <span class="p">{</span> <span class="p">{}</span> <span class="p">}</span>
997
+
998
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
999
+ <span class="n">jsonapi_get</span> <span class="s2">"/api/v1/employees/</span><span class="si">#{</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="ss">params: </span><span class="n">params</span>
1000
+ <span class="k">end</span>
1001
+
1002
+ <span class="n">describe</span> <span class="s1">'basic fetch'</span> <span class="k">do</span>
1003
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1004
+
1005
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
1006
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:find</span><span class="p">).</span><span class="nf">and_call_original</span>
1007
+ <span class="n">make_request</span>
1008
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
1009
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">jsonapi_type</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'employees'</span><span class="p">)</span>
1010
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">id</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
1011
+ <span class="k">end</span>
1012
+ <span class="k">end</span>
1013
+ <span class="k">end</span></code></pre></figure>
1014
+
1015
+ <p>Similar to <code class="language-plaintext highlighter-rouge">index</code>, but fetching only a single Employee.</p>
1016
+
1017
+ <a class="anchor" id="writes" />
1018
+ <a class="header" href="#writes">
1019
+ <h3>
1020
+ 4.2 Writes
1021
+ </h3>
1022
+ </a>
1023
+
1024
+ <a class="anchor" id="create" />
1025
+ <a class="header" href="#create">
1026
+ <h4>
1027
+ 4.2.1 #create
1028
+ </h4>
1029
+ </a>
1030
+
1031
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'rails_helper'</span>
1032
+
1033
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="s2">"employees#create"</span><span class="p">,</span> <span class="ss">type: :request</span> <span class="k">do</span>
1034
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
1035
+ <span class="n">jsonapi_post</span> <span class="s2">"/api/v1/employees"</span><span class="p">,</span> <span class="n">payload</span>
1036
+ <span class="k">end</span>
1037
+
1038
+ <span class="n">describe</span> <span class="s1">'basic create'</span> <span class="k">do</span>
1039
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
1040
+ <span class="p">{</span>
1041
+ <span class="ss">data: </span><span class="p">{</span>
1042
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
1043
+ <span class="ss">attributes: </span><span class="p">{</span>
1044
+ <span class="ss">first_name: </span><span class="s1">'Jane'</span>
1045
+ <span class="p">}</span>
1046
+ <span class="p">}</span>
1047
+ <span class="p">}</span>
1048
+ <span class="k">end</span>
1049
+
1050
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
1051
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:build</span><span class="p">).</span><span class="nf">and_call_original</span>
1052
+ <span class="n">expect</span> <span class="p">{</span>
1053
+ <span class="n">make_request</span>
1054
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
1055
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
1056
+ <span class="k">end</span>
1057
+ <span class="k">end</span>
1058
+ <span class="k">end</span></code></pre></figure>
1059
+
1060
+ <p>Here we’re ensuring EmployeeResource is called, a record is correctly
1061
+ inserted, and the response code is <code class="language-plaintext highlighter-rouge">201</code>.</p>
1062
+
1063
+ <p>You probably only want to add attributes required to pass validation,
1064
+ here - note that we don’t assert on attributes of the created record
1065
+ (save this for your Resource test). One easy way to do this is to pass
1066
+ randomized data from your factory:</p>
1067
+
1068
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
1069
+ <span class="p">{</span>
1070
+ <span class="ss">data: </span><span class="p">{</span>
1071
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
1072
+ <span class="ss">attributes: </span><span class="n">attributes_for</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span>
1073
+ <span class="p">}</span>
1074
+ <span class="p">}</span>
1075
+ <span class="k">end</span></code></pre></figure>
1076
+
1077
+ <p>See also:</p>
1078
+
1079
+ <ul>
1080
+ <li><a href="#required-belongs-to">Dealing with required belongs_to relationships</a>.</li>
1081
+ </ul>
1082
+
1083
+ <a class="anchor" id="update" />
1084
+ <a class="header" href="#update">
1085
+ <h4>
1086
+ 4.2.2 #update
1087
+ </h4>
1088
+ </a>
1089
+
1090
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'rails_helper'</span>
1091
+
1092
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="s2">"employees#update"</span><span class="p">,</span> <span class="ss">type: :request</span> <span class="k">do</span>
1093
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
1094
+ <span class="n">jsonapi_put</span> <span class="s2">"/api/v1/employees/</span><span class="si">#{</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">payload</span>
1095
+ <span class="k">end</span>
1096
+
1097
+ <span class="n">describe</span> <span class="s1">'basic update'</span> <span class="k">do</span>
1098
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1099
+
1100
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
1101
+ <span class="p">{</span>
1102
+ <span class="ss">data: </span><span class="p">{</span>
1103
+ <span class="ss">id: </span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">.</span><span class="nf">to_s</span><span class="p">,</span>
1104
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
1105
+ <span class="ss">attributes: </span><span class="p">{</span>
1106
+ <span class="ss">first_name: </span><span class="s1">'changed!'</span>
1107
+ <span class="p">}</span>
1108
+ <span class="p">}</span>
1109
+ <span class="p">}</span>
1110
+ <span class="k">end</span>
1111
+
1112
+ <span class="n">it</span> <span class="s1">'updates the resource'</span> <span class="k">do</span>
1113
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:find</span><span class="p">).</span><span class="nf">and_call_original</span>
1114
+ <span class="n">expect</span> <span class="p">{</span>
1115
+ <span class="n">make_request</span>
1116
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="n">employee</span><span class="p">.</span><span class="nf">reload</span><span class="p">.</span><span class="nf">attributes</span> <span class="p">}</span>
1117
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
1118
+ <span class="k">end</span>
1119
+ <span class="k">end</span>
1120
+ <span class="k">end</span></code></pre></figure>
1121
+
1122
+ <p>Here we’re ensuring EmployeeResource is called, attributes are updated,
1123
+ and we respond with a 201. Note that we don’t assert on specific
1124
+ attributes - save that for your Resource test.</p>
1125
+
1126
+ <p>Just like the prior section, you may want to leverage FactoryBot here to
1127
+ generate randomized attributes:</p>
1128
+
1129
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
1130
+ <span class="p">{</span>
1131
+ <span class="ss">data: </span><span class="p">{</span>
1132
+ <span class="ss">id: </span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="p">.</span><span class="nf">to_s</span><span class="p">,</span>
1133
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
1134
+ <span class="ss">attributes: </span><span class="n">attributes_for</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span>
1135
+ <span class="p">}</span>
1136
+ <span class="p">}</span>
1137
+ <span class="k">end</span></code></pre></figure>
1138
+
1139
+ <a class="anchor" id="destroy" />
1140
+ <a class="header" href="#destroy">
1141
+ <h4>
1142
+ 4.2.3 #destroy
1143
+ </h4>
1144
+ </a>
1145
+
1146
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'rails_helper'</span>
1147
+
1148
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="s2">"employees#destroy"</span><span class="p">,</span> <span class="ss">type: :request</span> <span class="k">do</span>
1149
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
1150
+ <span class="n">jsonapi_delete</span> <span class="s2">"/api/v1/employees/</span><span class="si">#{</span><span class="n">employee</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span>
1151
+ <span class="k">end</span>
1152
+
1153
+ <span class="n">describe</span> <span class="s1">'basic destroy'</span> <span class="k">do</span>
1154
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1155
+
1156
+ <span class="n">it</span> <span class="s1">'updates the resource'</span> <span class="k">do</span>
1157
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:find</span><span class="p">).</span><span class="nf">and_call_original</span>
1158
+ <span class="n">expect</span> <span class="p">{</span> <span class="n">make_request</span> <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
1159
+ <span class="n">expect</span> <span class="p">{</span> <span class="n">employee</span><span class="p">.</span><span class="nf">reload</span> <span class="p">}</span>
1160
+ <span class="p">.</span><span class="nf">to</span> <span class="n">raise_error</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">RecordNotFound</span><span class="p">)</span>
1161
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
1162
+ <span class="n">expect</span><span class="p">(</span><span class="n">json</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'meta'</span> <span class="o">=&gt;</span> <span class="p">{})</span>
1163
+ <span class="k">end</span>
1164
+ <span class="k">end</span>
1165
+ <span class="k">end</span></code></pre></figure>
1166
+
1167
+ <p>Here we’re sending a DELETE request, ensuring the record is actually
1168
+ removed, and we respond <a href="http://jsonapi.org/format/#crud-deleting-responses-200">according to the JSONAPI specification</a>.</p>
1169
+
1170
+ <a class="anchor" id="context" />
1171
+ <a class="header" href="#context">
1172
+ <h2>
1173
+ 5 Context
1174
+ </h2>
1175
+ </a>
1176
+
1177
+ <p>Occasionally you’ll need to set context for tests. The most common
1178
+ scenario is authorization:</p>
1179
+
1180
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:salary</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">readable: :admin?</span>
1181
+
1182
+ <span class="k">def</span> <span class="nf">admin?</span>
1183
+ <span class="n">context</span><span class="p">.</span><span class="nf">current_user</span><span class="p">.</span><span class="nf">admin?</span>
1184
+ <span class="k">end</span></code></pre></figure>
1185
+
1186
+ <p>When using Rails, <code class="language-plaintext highlighter-rouge">context</code> is the controller associated to the request.
1187
+ We can manually set context in tests:</p>
1188
+
1189
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="n">double</span><span class="p">(</span><span class="ss">admin?: </span><span class="kp">true</span><span class="p">)</span> <span class="p">}</span>
1190
+ <span class="n">let</span><span class="p">(</span><span class="ss">:ctx</span><span class="p">)</span> <span class="p">{</span> <span class="n">double</span><span class="p">(</span><span class="ss">current_user: </span><span class="n">user</span><span class="p">)</span> <span class="p">}</span>
1191
+
1192
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
1193
+ <span class="no">Graphiti</span><span class="p">.</span><span class="nf">with_context</span> <span class="n">ctx</span> <span class="k">do</span>
1194
+ <span class="n">render</span>
1195
+ <span class="k">end</span>
1196
+ <span class="n">expect</span><span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">salary</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">100_000</span><span class="p">)</span>
1197
+ <span class="k">end</span></code></pre></figure>
1198
+
1199
+ <p><br /></p>
1200
+
1201
+ <a class="anchor" id="schema-validation" />
1202
+ <a class="header" href="#schema-validation">
1203
+ <h2>
1204
+ 6 Schema Validation
1205
+ </h2>
1206
+ </a>
1207
+
1208
+ <p>Graphiti comes with built-in backwards-compatibility tests. We do this
1209
+ by comparing the current version of the schema with one previously
1210
+ checked-in.</p>
1211
+
1212
+ <p>These tests are added at the bottom of <code class="language-plaintext highlighter-rouge">spec/rails_helper.rb</code>:</p>
1213
+
1214
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Graphiti</span><span class="o">::</span><span class="no">SpecHelpers</span><span class="o">::</span><span class="no">RSpec</span><span class="p">.</span><span class="nf">schema!</span></code></pre></figure>
1215
+
1216
+ <p>Whenever you run tests, the schema check will <em>also</em> run. If we find any
1217
+ backwards-incompatibilities - attributes removed, types changed, default
1218
+ sort direction modified, etc - the schema test will fail with an output
1219
+ detailing all incompatibilities.</p>
1220
+
1221
+ <p>When the schema test succeeds, it will overwrite the existing schema
1222
+ file with the new schema. It will not do this on failure.</p>
1223
+
1224
+ <p>There are times when you want to accept an incompatibility and move on
1225
+ anyway. In this case, use <code class="language-plaintext highlighter-rouge">FORCE_SCHEMA</code>:</p>
1226
+
1227
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ FORCE_SCHEMA</span><span class="o">=</span><span class="nb">true </span>bin/rspec</code></pre></figure>
1228
+
1229
+ <p><br /></p>
1230
+
1231
+ <a class="anchor" id="testing-spectrum" />
1232
+ <a class="header" href="#testing-spectrum">
1233
+ <h2>
1234
+ 7 Testing Spectrum
1235
+ </h2>
1236
+ </a>
1237
+
1238
+ <p>Testing standards vary from team to team, and there is no right answer
1239
+ when judging “the right level of testing”.</p>
1240
+
1241
+ <p>You <em>could</em> add tests for every attribute, validating every sort and
1242
+ filter. Or, you could consider logicless configuration tested as part of
1243
+ Graphiti itself (the same way we don’t tend to test a <code class="language-plaintext highlighter-rouge">has_many</code>
1244
+ ActiveRecord relationship). Though our guides favor the latter, the
1245
+ extra tests could prove useful when performing a major upgrade or
1246
+ swapping datastores.</p>
1247
+
1248
+ <p>You <em>could</em> do more API testing, particularly for high-value
1249
+ functionality. Testing fully end-to-end, from middleware to response
1250
+ codes, gives a high level of confidence. But it can also feel like
1251
+ duplicate tests across endpoints, which is why we have Resource tests.</p>
1252
+
1253
+ <p>Graphiti provides sensible defaults, but you’re encouraged to consider
1254
+ the tradeoffs and pick the right level of testing for <em>you</em>.</p>
1255
+
1256
+ <a class="anchor" id="double-testing-units" />
1257
+ <a class="header" href="#double-testing-units">
1258
+ <h2>
1259
+ 8 Double-Testing Units
1260
+ </h2>
1261
+ </a>
1262
+
1263
+ <p>Integration testing is great: it gives a high level of confidence, and
1264
+ they’re typically the easiest tests to write. In fact, these tests are
1265
+ so powerful the value of unit testing sometimes comes up for debate.</p>
1266
+
1267
+ <p>Consider a custom filter powered by an ActiveRecord scope:</p>
1268
+
1269
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/employee_resource.rb</span>
1270
+ <span class="n">filter</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
1271
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
1272
+ <span class="n">scope</span><span class="p">.</span><span class="nf">by_title</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
1273
+ <span class="k">end</span>
1274
+ <span class="k">end</span>
1275
+
1276
+ <span class="c1"># app/models/employee.rb</span>
1277
+ <span class="n">scope</span> <span class="ss">:by_title</span><span class="p">,</span> <span class="o">-&gt;</span><span class="p">(</span><span class="n">title</span><span class="p">)</span> <span class="p">{</span>
1278
+ <span class="n">joins</span><span class="p">(</span><span class="ss">:current_position</span><span class="p">)</span>
1279
+ <span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"lower(title) = ?"</span><span class="p">,</span> <span class="n">title</span><span class="p">.</span><span class="nf">downcase</span><span class="p">)</span>
1280
+ <span class="p">}</span></code></pre></figure>
1281
+
1282
+ <p>If we’re by-the-book, we should absolutely test <code class="language-plaintext highlighter-rouge">.by_title</code> on the
1283
+ Employee model. After all, we’re exposing a public interface that other
1284
+ developers might rely on in the future.</p>
1285
+
1286
+ <p>This can feel cumbersome, even duplicative. The Resource Test of the
1287
+ title filter will seed the same data as the corresponding unit test, and
1288
+ the assertion will be almost identical. But because Resource Tests are
1289
+ <em>integration</em> tests, we shouldn’t mock the code either.</p>
1290
+
1291
+ <p>The best practice here is to use <a href="https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-context">RSpec shared_context</a> to remove the duplication:</p>
1292
+
1293
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># spec/support/employees_helper.rb</span>
1294
+ <span class="no">RSpec</span><span class="p">.</span><span class="nf">shared_context</span> <span class="s1">'employees by title'</span> <span class="k">do</span>
1295
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee1</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1296
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee2</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1297
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:employee3</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:employee</span><span class="p">)</span> <span class="p">}</span>
1298
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:position1</span><span class="p">)</span> <span class="k">do</span>
1299
+ <span class="n">create</span><span class="p">(</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">title: </span><span class="s1">'foo'</span><span class="p">,</span> <span class="ss">employee: </span><span class="n">employee1</span><span class="p">)</span>
1300
+ <span class="k">end</span>
1301
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:position2</span><span class="p">)</span> <span class="k">do</span>
1302
+ <span class="n">create</span><span class="p">(</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">title: </span><span class="s1">'BAR'</span><span class="p">,</span> <span class="ss">employee: </span><span class="n">employee2</span><span class="p">)</span>
1303
+ <span class="k">end</span>
1304
+ <span class="n">let!</span><span class="p">(</span><span class="ss">:position3</span><span class="p">)</span> <span class="k">do</span>
1305
+ <span class="n">create</span><span class="p">(</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">title: </span><span class="s1">'bar'</span><span class="p">,</span> <span class="ss">employee: </span><span class="n">employee3</span><span class="p">)</span>
1306
+ <span class="k">end</span>
1307
+ <span class="k">end</span>
1308
+
1309
+ <span class="c1"># spec/models/employee.rb</span>
1310
+ <span class="n">describe</span> <span class="s1">'.by_title'</span> <span class="k">do</span>
1311
+ <span class="n">include_context</span> <span class="s1">'employees by title'</span>
1312
+
1313
+ <span class="n">it</span> <span class="s1">'returns employees matching the given title'</span> <span class="k">do</span>
1314
+ <span class="n">expect</span><span class="p">(</span><span class="no">Employee</span><span class="p">.</span><span class="nf">by_title</span><span class="p">(</span><span class="s1">'bar'</span><span class="p">))</span>
1315
+ <span class="p">.</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="n">employee2</span><span class="p">,</span> <span class="n">employee3</span><span class="p">])</span>
1316
+ <span class="k">end</span>
1317
+ <span class="k">end</span>
1318
+
1319
+ <span class="c1"># spec/resources/employee_resource.rb</span>
1320
+ <span class="n">describe</span> <span class="s1">'filtering'</span> <span class="k">do</span>
1321
+ <span class="n">describe</span> <span class="s1">'by title'</span> <span class="k">do</span>
1322
+ <span class="n">include_context</span> <span class="s1">'employees by title'</span>
1323
+
1324
+ <span class="n">before</span> <span class="k">do</span>
1325
+ <span class="n">params</span><span class="p">[</span><span class="ss">:filter</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">title: </span><span class="s1">'bar'</span> <span class="p">}</span>
1326
+ <span class="k">end</span>
1327
+
1328
+ <span class="n">it</span> <span class="s1">'returns employees matching the given title'</span> <span class="k">do</span>
1329
+ <span class="n">expect</span><span class="p">(</span><span class="n">records</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="n">employee2</span><span class="p">,</span> <span class="n">employee3</span><span class="p">])</span>
1330
+ <span class="k">end</span>
1331
+ <span class="k">end</span>
1332
+ <span class="k">end</span></code></pre></figure>
1333
+
1334
+ <p>This <strong>allows our <code class="language-plaintext highlighter-rouge">by_title</code> scope to be re-used by future developers
1335
+ outside of the Resource context</strong>. It also keeps code clean and
1336
+ isolated.</p>
1337
+
1338
+ <p>But it’s not unreasonable to think the overhead here isn’t worth it. If
1339
+ you’re of this mind, we recommend testing the Resource and marking the
1340
+ method as not re-usable:</p>
1341
+
1342
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># @api private</span>
1343
+ <span class="n">scope</span> <span class="ss">:by_title</span><span class="p">,</span> <span class="o">-&gt;</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span></code></pre></figure>
1344
+
1345
+ <p>This way future developers know the scope is only an implementation
1346
+ detail and not considered part of this object’s public API. Writing the
1347
+ unit test can be deferred until the use case actually arises.</p>
1348
+
1349
+ <a class="anchor" id="generators" />
1350
+ <a class="header" href="#generators">
1351
+ <h2>
1352
+ 9 Generators
1353
+ </h2>
1354
+ </a>
1355
+
1356
+ <p>The <a href="/1.13/guides/concepts/resources#generators">Resource generator</a> will create both Resource and API tests for you.
1357
+ Use these as templates to implement your tests.</p>
1358
+
1359
+ <p>You can also run</p>
1360
+
1361
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails generate graphiti:api_test RESOURCE <span class="o">[</span>options]</code></pre></figure>
1362
+
1363
+ <p>For example</p>
1364
+
1365
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails generate graphiti:api_test EmployeeResource <span class="nt">-a</span> index show</code></pre></figure>
1366
+
1367
+ <p>To generate only the API tests. This can be particularly helpful because
1368
+ API tests are mostly boilerplate that does not need to be manually
1369
+ edited. Pass the <code class="language-plaintext highlighter-rouge">-a</code> option to limit RESTful actions.</p>
1370
+
1371
+ <p><br />
1372
+ <br /></p>
1373
+ </div>
1374
+
1375
+ </div>
1376
+ </div>
1377
+ </main>
1378
+ <div class="main-footer main-footer--dark">
1379
+ <div class="container">
1380
+ <div class="row">
1381
+ <div class="col-sm-4 menu">
1382
+ <h3>Overview</h3>
1383
+ <ul>
1384
+ <li>
1385
+ <a href="/1.13/quickstart">Quickstart</a>
1386
+ </li>
1387
+ <li>
1388
+ <a href="/1.13/tutorial">Tutorial</a>
1389
+ </li>
1390
+ <li>
1391
+ <a href="/1.13/guides">Guides</a>
1392
+ </li>
1393
+ </ul>
1394
+ </div>
1395
+ <div class="col-sm-4 menu">
1396
+ <h3>Contact</h3>
1397
+ <ul>
1398
+ <li>
1399
+ <a target="_blank" href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>
1400
+ </li>
1401
+ <li>
1402
+ <a href="mailto:richmolj@gmail.com">Email</a>
1403
+ </li>
1404
+ </ul>
1405
+ </div>
1406
+ <div class="col-sm-4 menu">
1407
+ <h3>Related</h3>
1408
+ <ul>
1409
+ <li>
1410
+ <a target="_blank" href="http://jsonapi.org">JSONAPI Spec</a>
1411
+ </li>
1412
+ <li>
1413
+ <a target="_blank" href="http://jsonapi-rb.org">jsonapi-rb</a>
1414
+ </li>
1415
+ <li>
1416
+ <a target="_blank" href="https://vuejs.org/">VueJS</a>
1417
+ </li>
1418
+ </ul>
1419
+ </div>
1420
+ </div>
1421
+ </div>
1422
+ </div>
1423
+
1424
+ <script type="text/javascript">
1425
+ $(function () {
1426
+
1427
+ var flipTabs = function() {
1428
+ var isTS = true;
1429
+ if (localStorage.getItem('js-lang') === 'javascript') {
1430
+ isTS = false;
1431
+ }
1432
+
1433
+ $('.code-tabs').each(function(index, el) {
1434
+ if (isTS) {
1435
+ console.log('hiding js');
1436
+ $($(el).children()[1]).hide();
1437
+ $($(el).children()[0]).show();
1438
+ } else {
1439
+ console.log('hiding ts');
1440
+ $($(el).children()[0]).hide();
1441
+ $($(el).children()[1]).show();
1442
+ }
1443
+ });
1444
+
1445
+ if (isTS) {
1446
+ $('.tab.typescript').addClass('active');
1447
+ $('.tab.javascript').removeClass('active');
1448
+ } else {
1449
+ $('.tab.typescript').removeClass('active');
1450
+ $('.tab.javascript').addClass('active');
1451
+ }
1452
+ }
1453
+
1454
+ $('.tab').click(function() {
1455
+ if ($(this).hasClass('typescript')) {
1456
+ localStorage.setItem('js-lang', 'typescript');
1457
+ } else {
1458
+ localStorage.setItem('js-lang', 'javascript');
1459
+ }
1460
+
1461
+ flipTabs();
1462
+ });
1463
+
1464
+ flipTabs();
1465
+ })
1466
+ </script>
1467
+
1468
+ </body>
1469
+ </html>