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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -86
- data/.github/workflows/docs.yml +60 -0
- data/.github/workflows/release.yml +8 -8
- data/.gitignore +7 -0
- data/.npmrc +9 -0
- data/.standard.yml +4 -1
- data/Appraisals +33 -32
- data/CHANGELOG.md +41 -0
- data/README.md +13 -2
- data/UPGRADING.md +2 -68
- data/docs/concepts/backends-and-models.md +122 -0
- data/docs/concepts/endpoints.md +183 -0
- data/docs/concepts/links.md +212 -0
- data/docs/concepts/overview.md +80 -0
- data/docs/concepts/persisting.md +376 -0
- data/docs/concepts/relationships.md +527 -0
- data/docs/concepts/resources.md +677 -0
- data/docs/getting-started/first-api.md +289 -0
- data/docs/getting-started/installation.md +185 -0
- data/docs/intro.md +307 -0
- data/docs/js/authentication.md +63 -0
- data/docs/js/ddau.md +20 -0
- data/docs/js/extra-params.md +41 -0
- data/docs/js/index.md +112 -0
- data/docs/js/installation.md +120 -0
- data/docs/js/middleware.md +72 -0
- data/docs/js/models.md +202 -0
- data/docs/js/reads.md +494 -0
- data/docs/js/state-syncing.md +100 -0
- data/docs/js/writes.md +373 -0
- data/docs/reference/vandal.md +63 -0
- data/docs/reference/why.md +13 -0
- data/docs/topics/authorization.md +155 -0
- data/docs/topics/caching.md +55 -0
- data/docs/topics/customizing-sideloads.md +156 -0
- data/docs/topics/debugging.md +216 -0
- data/docs/topics/error-handling.md +210 -0
- data/docs/topics/etags.md +46 -0
- data/docs/topics/hopping-relationships.md +149 -0
- data/docs/topics/json-attributes.md +77 -0
- data/docs/topics/openstruct-models.md +50 -0
- data/docs/topics/remote-resources.md +291 -0
- data/docs/topics/testing.md +894 -0
- data/docs/topics/without-activerecord.md +324 -0
- data/docs/tutorial/index.md +58 -0
- data/docs/tutorial/step_0.md +107 -0
- data/docs/tutorial/step_1.md +199 -0
- data/docs/tutorial/step_2.md +312 -0
- data/docs/tutorial/step_3.md +142 -0
- data/docs/tutorial/step_4.md +135 -0
- data/docs/tutorial/step_5.md +69 -0
- data/docs/tutorial/step_6.md +82 -0
- data/docs/tutorial/step_7.md +205 -0
- data/docs/tutorial/step_8.md +128 -0
- data/docs/tutorial/step_9.md +171 -0
- data/docs/upgrading.md +265 -0
- data/gemfiles/rails_7_1.gemfile +4 -3
- data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
- data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
- data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
- data/graphiti.gemspec +7 -5
- data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
- data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
- data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
- data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
- data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
- data/lib/graphiti/configuration.rb +2 -2
- data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
- data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
- data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
- data/lib/graphiti/error_serializers/validation.rb +143 -0
- data/lib/graphiti/errors.rb +4 -23
- data/lib/graphiti/query.rb +1 -1
- data/lib/graphiti/rails/context.rb +33 -0
- data/lib/graphiti/rails/controller.rb +41 -0
- data/lib/graphiti/rails/debugging.rb +18 -0
- data/lib/graphiti/rails/exception_handlers.rb +77 -0
- data/lib/graphiti/rails/railtie.rb +139 -0
- data/lib/graphiti/rails/responders.rb +21 -0
- data/lib/graphiti/rails/test_helpers.rb +22 -0
- data/lib/graphiti/rails.rb +47 -29
- data/lib/graphiti/resource/configuration.rb +1 -0
- data/lib/graphiti/resource/interface.rb +2 -2
- data/lib/graphiti/resource/persistence.rb +14 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +1 -1
- data/lib/graphiti/resource.rb +13 -1
- data/lib/graphiti/responders.rb +7 -20
- data/lib/graphiti/schema.rb +5 -1
- data/lib/graphiti/schema_diff.rb +4 -0
- data/lib/graphiti/scope.rb +45 -37
- data/lib/graphiti/serializer.rb +6 -0
- data/lib/graphiti/sideload/belongs_to.rb +38 -5
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
- data/lib/graphiti/sideload.rb +54 -35
- data/lib/graphiti/spec_helpers/errors.rb +73 -0
- data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
- data/lib/graphiti/spec_helpers/helpers.rb +107 -0
- data/lib/graphiti/spec_helpers/node.rb +88 -0
- data/lib/graphiti/spec_helpers/rspec.rb +147 -0
- data/lib/graphiti/spec_helpers.rb +53 -0
- data/lib/graphiti/util/include_params.rb +2 -2
- data/lib/graphiti/util/persistence.rb +10 -11
- data/lib/graphiti/util/serializer_relationships.rb +41 -5
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti-rails.rb +11 -0
- data/lib/graphiti.rb +34 -10
- data/lib/graphiti_errors.rb +11 -0
- data/lib/graphiti_spec_helpers/rspec.rb +3 -0
- data/lib/graphiti_spec_helpers.rb +11 -0
- data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
- data/package-lock.json +6199 -0
- data/package.json +5 -4
- data/website/.gitignore +20 -0
- data/website/README.md +43 -0
- data/website/docusaurus.config.js +141 -0
- data/website/package-lock.json +19474 -0
- data/website/package.json +46 -0
- data/website/sidebars.js +82 -0
- data/website/src/css/custom.css +58 -0
- data/website/src/pages/markdown-page.mdx +7 -0
- data/website/static/.nojekyll +0 -0
- data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
- data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
- data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
- data/website/static/1.13/2019/10/14/tutorial.html +198 -0
- data/website/static/1.13/CNAME +1 -0
- data/website/static/1.13/README.md +16 -0
- data/website/static/1.13/assets/css/syntax.css +60 -0
- data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
- data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
- data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
- data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
- data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
- data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
- data/website/static/1.13/assets/favicons/favicon.ico +0 -0
- data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
- data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
- data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
- data/website/static/1.13/assets/img/backend.gif +0 -0
- data/website/static/1.13/assets/img/conformity.png +0 -0
- data/website/static/1.13/assets/img/error_payload.png +0 -0
- data/website/static/1.13/assets/img/gh.png +0 -0
- data/website/static/1.13/assets/img/lifecycle.gif +0 -0
- data/website/static/1.13/assets/img/logo-500.png +0 -0
- data/website/static/1.13/assets/img/logo.png +0 -0
- data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/meta_total_count.png +0 -0
- data/website/static/1.13/assets/img/persist.jpg +0 -0
- data/website/static/1.13/assets/img/resource.gif +0 -0
- data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/rest1.gif +0 -0
- data/website/static/1.13/assets/img/rest2.gif +0 -0
- data/website/static/1.13/assets/img/rest3.gif +0 -0
- data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/why.png +0 -0
- data/website/static/1.13/assets/js/highlight.pack.js +2 -0
- data/website/static/1.13/assets/main.css +15518 -0
- data/website/static/1.13/assets/main.css.map +1 -0
- data/website/static/1.13/bin/bundle +109 -0
- data/website/static/1.13/bin/jekyll +27 -0
- data/website/static/1.13/bin/kramdown +27 -0
- data/website/static/1.13/bin/listen +27 -0
- data/website/static/1.13/bin/rake +27 -0
- data/website/static/1.13/bin/rougify +27 -0
- data/website/static/1.13/bin/safe_yaml +27 -0
- data/website/static/1.13/bin/sass +27 -0
- data/website/static/1.13/bin/sass-convert +27 -0
- data/website/static/1.13/bin/scss +27 -0
- data/website/static/1.13/blog.html +259 -0
- data/website/static/1.13/cheatsheet.html +316 -0
- data/website/static/1.13/cookbooks/authorization.md +0 -0
- data/website/static/1.13/cookbooks/caching.md +0 -0
- data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
- data/website/static/1.13/cookbooks/etags.md +0 -0
- data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
- data/website/static/1.13/cookbooks/json_attributes.md +0 -0
- data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
- data/website/static/1.13/cookbooks/remote-resources.md +0 -0
- data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
- data/website/static/1.13/features.html +249 -0
- data/website/static/1.13/feed.xml +106 -0
- data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
- data/website/static/1.13/guides/concepts/debugging.html +440 -0
- data/website/static/1.13/guides/concepts/endpoints.html +432 -0
- data/website/static/1.13/guides/concepts/error-handling.html +396 -0
- data/website/static/1.13/guides/concepts/links.html +501 -0
- data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
- data/website/static/1.13/guides/concepts/resources.html +2176 -0
- data/website/static/1.13/guides/concepts/testing.html +1469 -0
- data/website/static/1.13/guides/getting-started/installation.html +420 -0
- data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
- data/website/static/1.13/guides/index.html +269 -0
- data/website/static/1.13/guides/overview.html +325 -0
- data/website/static/1.13/guides/upgrading-2-0.html +193 -0
- data/website/static/1.13/guides/upgrading.html +314 -0
- data/website/static/1.13/guides/vandal.html +282 -0
- data/website/static/1.13/guides/why.html +1121 -0
- data/website/static/1.13/index.html +72 -0
- data/website/static/1.13/js/authentication.html +295 -0
- data/website/static/1.13/js/ddau.html +238 -0
- data/website/static/1.13/js/extra-params.html +270 -0
- data/website/static/1.13/js/index.html +321 -0
- data/website/static/1.13/js/installation.html +637 -0
- data/website/static/1.13/js/introduction.html +257 -0
- data/website/static/1.13/js/middleware.html +318 -0
- data/website/static/1.13/js/reads/fieldsets.html +271 -0
- data/website/static/1.13/js/reads/filtering.html +289 -0
- data/website/static/1.13/js/reads/includes.html +260 -0
- data/website/static/1.13/js/reads/index.html +497 -0
- data/website/static/1.13/js/reads/nested-queries.html +353 -0
- data/website/static/1.13/js/reads/pagination.html +260 -0
- data/website/static/1.13/js/reads/sorting.html +265 -0
- data/website/static/1.13/js/reads/statistics.html +289 -0
- data/website/static/1.13/js/state-syncing.html +340 -0
- data/website/static/1.13/js/writes/deferred.html +296 -0
- data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
- data/website/static/1.13/js/writes/index.html +391 -0
- data/website/static/1.13/js/writes/nested.html +330 -0
- data/website/static/1.13/js/writes/validations.html +272 -0
- data/website/static/1.13/quickstart.html +660 -0
- data/website/static/1.13/template +161 -0
- data/website/static/1.13/tutorial/index.html +250 -0
- data/website/static/1.13/tutorial/step_0.html +292 -0
- data/website/static/1.13/tutorial/step_1.html +517 -0
- data/website/static/1.13/tutorial/step_2.html +481 -0
- data/website/static/1.13/tutorial/step_3.html +323 -0
- data/website/static/1.13/tutorial/step_4.html +318 -0
- data/website/static/1.13/tutorial/step_5.html +265 -0
- data/website/static/1.13/tutorial/step_6.html +276 -0
- data/website/static/1.13/tutorial/step_7.html +390 -0
- data/website/static/1.13/tutorial/step_8.html +316 -0
- data/website/static/1.13/tutorial/step_9.html +365 -0
- data/website/static/assets/img/error_payload.png +0 -0
- data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
- data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
- data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
- data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
- data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
- data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
- data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
- data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
- data/website/static/assets/img/meta_total_count.png +0 -0
- data/website/static/img/docusaurus-social-card.jpg +0 -0
- data/website/static/img/docusaurus.png +0 -0
- data/website/static/img/favicon.ico +0 -0
- data/website/static/img/logo.png +0 -0
- data/website/static/img/logo.svg +1 -0
- data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
- data/website/static/img/undraw_docusaurus_react.svg +170 -0
- data/website/static/img/undraw_docusaurus_tree.svg +40 -0
- metadata +245 -46
- data/gemfiles/rails_6.gemfile +0 -18
- data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7.gemfile +0 -18
- data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
- data/lib/graphiti/railtie.rb +0 -121
- /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
- /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">&</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"># => { 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">&</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">&</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">&</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">&</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">&</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">&</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">=></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">-></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">-></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>
|