@@ -4,8 +4,8 @@ The following example demonstrates steps how we describe and test our API using
4
4
5
5
### Step 1
6
6
7
- Describe our feature. Imagine we need a REST API with ** json** format. Lets from the point, that
8
- we need to have a ** /version** endpoint, which responds with a version number. We also need to manage
7
+ Describe our feature. Imagine we need a REST API with ` json ` format. Lets from the point, that
8
+ we need to have a ` /version ` endpoint, which responds with a version number. We also need to manage
9
9
error responses.
10
10
11
11
``` gherkin
@@ -31,24 +31,24 @@ Feature: get version
31
31
And the response should match json:
32
32
"""
33
33
{
34
- "version": "v0.5.3 "
34
+ "version": "v0.0.0-dev "
35
35
}
36
36
"""
37
37
```
38
38
39
- Save it as ** version.feature** .
39
+ Save it as ` features/ version.feature` .
40
40
Now we have described a success case and an error when the request method is not allowed.
41
41
42
42
### Step 2
43
43
44
- Run ** godog version.feature ** . You should see the following result, which says that all of our
44
+ Execute ` godog run ` . You should see the following result, which says that all of our
45
45
steps are yet undefined and provide us with the snippets to implement them.
46
46
47
47
![ Screenshot] ( https://raw.github.com/cucumber/godog/master/_examples/api/screenshots/undefined.png )
48
48
49
49
### Step 3
50
50
51
- Lets copy the snippets to ** api_test.go** and modify it for our use case. Since we know that we will
51
+ Lets copy the snippets to ` api_test.go ` and modify it for our use case. Since we know that we will
52
52
need to store state within steps (a response), we should introduce a structure with some variables.
53
53
54
54
``` go
@@ -74,6 +74,21 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) error {
74
74
return godog.ErrPending
75
75
}
76
76
77
+ func TestFeatures (t *testing .T ) {
78
+ suite := godog.TestSuite {
79
+ ScenarioInitializer: InitializeScenario,
80
+ Options: &godog.Options {
81
+ Format: " pretty" ,
82
+ Paths: []string {" features" },
83
+ TestingT: t, // Testing instance that will run subtests.
84
+ },
85
+ }
86
+
87
+ if suite.Run () != 0 {
88
+ t.Fatal (" non-zero status returned, failed to run feature tests" )
89
+ }
90
+ }
91
+
77
92
func InitializeScenario (s *godog .ScenarioContext ) {
78
93
api := &apiFeature{}
79
94
s.Step (` ^I send "([^"]*)" request to "([^"]*)"$` , api.iSendrequestTo )
@@ -84,19 +99,20 @@ func InitializeScenario(s *godog.ScenarioContext) {
84
99
85
100
### Step 4
86
101
87
- Now we can implemented steps, since we know what behavior we expect:
102
+ Now we can implement steps, since we know what behavior we expect:
88
103
89
104
``` go
90
105
// file: api_test.go
91
106
package main
92
107
93
108
import (
94
109
" context"
95
- " bytes"
96
110
" encoding/json"
97
111
" fmt"
98
112
" net/http"
99
113
" net/http/httptest"
114
+ " reflect"
115
+ " testing"
100
116
101
117
" github.com/cucumber/godog"
102
118
)
@@ -105,7 +121,7 @@ type apiFeature struct {
105
121
resp *httptest.ResponseRecorder
106
122
}
107
123
108
- func (a *apiFeature ) resetResponse (interface {} ) {
124
+ func (a *apiFeature ) resetResponse (* godog . Scenario ) {
109
125
a.resp = httptest.NewRecorder ()
110
126
}
111
127
@@ -141,48 +157,67 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
141
157
return nil
142
158
}
143
159
144
- func (a *apiFeature ) theResponseShouldMatchJSON (body *godog .DocString ) error {
145
- var expected , actual []byte
146
- var data interface {}
147
- if err = json.Unmarshal ([]byte (body.Content ), &data); err != nil {
160
+ func (a *apiFeature ) theResponseShouldMatchJSON (body *godog .DocString ) (err error ) {
161
+ var expected , actual interface {}
162
+
163
+ // re-encode expected response
164
+ if err = json.Unmarshal ([]byte (body.Content ), &expected); err != nil {
148
165
return
149
166
}
150
- if expected, err = json.Marshal (data); err != nil {
167
+
168
+ // re-encode actual response too
169
+ if err = json.Unmarshal (a.resp .Body .Bytes (), &actual); err != nil {
151
170
return
152
171
}
153
- actual = a.resp .Body .Bytes ()
154
- if !bytes.Equal (actual, expected) {
155
- err = fmt.Errorf (" expected json, does not match actual: %s " , string (actual))
172
+
173
+ // the matching may be adapted per different requirements.
174
+ if !reflect.DeepEqual (expected, actual) {
175
+ return fmt.Errorf (" expected JSON does not match actual, %v vs. %v " , expected, actual)
156
176
}
157
- return
177
+ return nil
158
178
}
159
179
160
- func InitializeScenario (s *godog .ScenarioContext ) {
180
+ func TestFeatures (t *testing .T ) {
181
+ suite := godog.TestSuite {
182
+ ScenarioInitializer: InitializeScenario,
183
+ Options: &godog.Options {
184
+ Format: " pretty" ,
185
+ Paths: []string {" features" },
186
+ TestingT: t, // Testing instance that will run subtests.
187
+ },
188
+ }
189
+
190
+ if suite.Run () != 0 {
191
+ t.Fatal (" non-zero status returned, failed to run feature tests" )
192
+ }
193
+ }
194
+
195
+ func InitializeScenario (ctx *godog .ScenarioContext ) {
161
196
api := &apiFeature{}
162
197
163
198
ctx.Before (func (ctx context.Context , sc *godog.Scenario ) (context.Context , error ) {
164
199
api.resetResponse (sc)
165
200
return ctx, nil
166
201
})
167
-
168
- s.Step (` ^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$` , api.iSendrequestTo )
169
- s.Step (` ^the response code should be (\d+)$` , api.theResponseCodeShouldBe )
170
- s.Step (` ^the response should match json:$` , api.theResponseShouldMatchJSON )
202
+ ctx.Step (` ^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$` , api.iSendrequestTo )
203
+ ctx.Step (` ^the response code should be (\d+)$` , api.theResponseCodeShouldBe )
204
+ ctx.Step (` ^the response should match json:$` , api.theResponseShouldMatchJSON )
171
205
}
172
206
```
173
207
174
- ** NOTE:** the ` getVersion ` handler call on ** /version** endpoint. We actually need to implement it now.
208
+ ** NOTE:** the ` getVersion ` handler is called on ` /version ` endpoint.
209
+ Executing ` godog run ` or ` go test -v ` will provide ` undefined: getVersion ` error, so we actually need to implement it now.
175
210
If we made some mistakes in step implementations, we will know about it when we run the tests.
176
211
177
- Though, we could also improve our ** JSON** comparison function to range through the interfaces and
212
+ Though, we could also improve our ` JSON ` comparison function to range through the interfaces and
178
213
match their types and values.
179
214
180
215
In case if some router is used, you may search the handler based on the endpoint. Current example
181
216
uses a standard http package.
182
217
183
218
### Step 5
184
219
185
- Finally, lets implement the ** api ** server:
220
+ Finally, lets implement the ` API ` server:
186
221
187
222
``` go
188
223
// file: api.go
@@ -191,60 +226,52 @@ package main
191
226
192
227
import (
193
228
" encoding/json"
194
- " fmt"
195
229
" net/http"
196
230
197
231
" github.com/cucumber/godog"
198
232
)
199
233
200
234
func getVersion (w http .ResponseWriter , r *http .Request ) {
201
- if r.Method != " GET " {
235
+ if r.Method != http. MethodGet {
202
236
fail (w, " Method not allowed" , http.StatusMethodNotAllowed )
203
237
return
204
238
}
239
+
205
240
data := struct {
206
241
Version string ` json:"version"`
207
242
}{Version: godog.Version }
208
243
209
244
ok (w, data)
210
245
}
211
246
212
- func main () {
213
- http.HandleFunc (" /version" , getVersion)
214
- http.ListenAndServe (" :8080" , nil )
215
- }
216
-
217
247
// fail writes a json response with error msg and status header
218
248
func fail (w http .ResponseWriter , msg string , status int ) {
219
- w.Header (). Set ( " Content-Type " , " application/json " )
249
+ w.WriteHeader (status )
220
250
221
251
data := struct {
222
252
Error string ` json:"error"`
223
253
}{Error: msg}
224
-
225
254
resp , _ := json.Marshal (data)
226
- w.WriteHeader (status)
227
255
228
- fmt.Fprintf (w, string (resp))
256
+ w.Header ().Set (" Content-Type" , " application/json" )
257
+ w.Write (resp)
229
258
}
230
259
231
260
// ok writes data to response with 200 status
232
261
func ok (w http .ResponseWriter , data interface {}) {
233
- w.Header ().Set (" Content-Type" , " application/json" )
234
-
235
- if s , ok := data.(string ); ok {
236
- fmt.Fprintf (w, s)
237
- return
238
- }
239
-
240
262
resp , err := json.Marshal (data)
241
263
if err != nil {
242
- w.WriteHeader (http.StatusInternalServerError )
243
- fail (w, " oops something evil has happened" , 500 )
264
+ fail (w, " Oops something evil has happened" , http.StatusInternalServerError )
244
265
return
245
266
}
246
267
247
- fmt.Fprintf (w, string (resp))
268
+ w.Header ().Set (" Content-Type" , " application/json" )
269
+ w.Write (resp)
270
+ }
271
+
272
+ func main () {
273
+ http.HandleFunc (" /version" , getVersion)
274
+ http.ListenAndServe (" :8080" , nil )
248
275
}
249
276
```
250
277
@@ -253,7 +280,7 @@ used to respond with the correct constant version number.
253
280
254
281
### Step 6
255
282
256
- Run our tests to see whether everything is happening as we have expected: ` godog version.feature `
283
+ Run our tests to see whether everything is happening as we have expected: ` go test -v `
257
284
258
285
![ Screenshot] ( https://raw.github.com/cucumber/godog/master/_examples/api/screenshots/passed.png )
259
286
0 commit comments