-
Notifications
You must be signed in to change notification settings - Fork 66
/
testing_actions.cr
152 lines (109 loc) · 4.42 KB
/
testing_actions.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
class Guides::Testing::TestingActions < GuideAction
guide_route "/testing/testing-actions"
def self.title
"Testing Actions"
end
def markdown : String
<<-MD
## Setup
Located in your app's `spec/support/` directory is an `ApiClient` that you can use to make requests
to your app's Action classes. Use this object to set any custom headers, or params you need.
### Making requests
All requests can be made by using `ApiClient.exec`, and passing the action class. Lucky will automatically
infer which HTTP method to use based on route defined in the Action class.
```crystal
# GET /users
ApiClient.exec(Users::Index)
# POST /posts
ApiClient.exec(Posts::Create)
```
You can also pass path and query params using `with` if your Action requires them.
```crystal
# PUT /users/1
ApiClient.exec(Users::Update.with(user.id))
# DELETE /posts/3
ApiClient.exec(Posts::Delete.with(post.id))
# or pass additional query params
ApiClient.exec(Posts::Search.with(q: "Lucky"))
```
Read more on [URL generation](#{Guides::HttpAndRouting::LinkGeneration.path}).
### Setting body params
The second argument to the `exec` method will take a `NamedTuple` of params you want to send.
In most cases, these params will be sent to an [Operation](#{Guides::Database::SavingRecords.path}),
so these params will need to be nested with the proper [param_key](#{Guides::Database::SavingRecords.path(anchor: Guides::Database::SavingRecords::ANCHOR_PARAM_KEY)}).
```crystal
ApiClient.exec(Posts::Create, post: {title: "My next Taco Dish", posted_at: 1.day.ago})
ApiClient.exec(Users::Update.with(user.id), user: {email: "[email protected]"})
```
### Setting headers
If you need to set custom headers, you'll use the `headers` method on an instance of the `ApiClient`.
```crystal
client = ApiClient.new
client
.headers("Accept": "application/vnd.api.v1+json")
.headers("Set-Cookie": "remember_me=1")
.headers("Authorization": "Bearer abc123")
# Then make your request:
client.exec(Api::Users::Index)
```
### Creating methods for common setup
Let's say your API uses a Range header to determine which range of items should be included.
We can create a method on our `ApiClient` to make this easier to reuse.
```crystal
# spec/support/api_client.cr
class ApiClient < Lucky::BaseHTTPClient
# ...
def page(page : Int32, per_page = 10)
# Set pagination headers
headers("Range": "order,id \#{page * per_page}; order=desc,max=\#{per_page}"
end
end
```
Now we can use this in our tests
```crystal
response = ApiClient.new.page(2).exec(Api::Users::Index)
```
### User Auth
If you generated your app with User Authentication, your API endpoints may require being
authenticated to access those endpoints. You can use the `ApiClient.auth(user)` method
to set the proper header.
```crystal
user = UserFactory.create
ApiClient.auth(user).exec(Api::Records::Index)
```
## JSON Actions
If you need to test your JSON API, or any action that has a JSON response, Lucky gives you some
built-in helper methods to make testing responses easier.
We will use this action as an example:
```crystal
class Api::Rockets::Show < ApiAction
get "/api/rockets/:rocket_id" do
rocket = RocketQuery.find(rocket_id)
json({id: rocket.id, type: rocket.type, name: rocket.name})
end
end
```
```crystal
# spec/requests/api/rockets/show_spec.cr
require "../../../spec_helper"
describe Api::Rockets::Show do
it "returns a 200 response with the rocket name" do
rocket = RocketFactory.create &.name("Dragon 1")
response = ApiClient.auth(current_user).exec(Api::Rockets::Show.with(rocket.id))
# The JSON response should have the key `name` with a value `"Dragon 1"`.
response.should send_json(200, name: "Dragon 1")
end
it "returns a 401 for unauthenticated requests" do
response = ApiClient.exec(Api::Rockets::Show.with(4))
response.status_code.should eq(401)
end
end
private def current_user : User
UserFactory.create &.email("[email protected]")
end
```
The `send_json` method will check that the HTTP status matches, and the JSON response
contains your expected values.
MD
end
end