Skip to content

Commit 75d5cab

Browse files
authored
fix(examples): update api example (#532)
1 parent 3e0f902 commit 75d5cab

File tree

5 files changed

+104
-69
lines changed

5 files changed

+104
-69
lines changed

_examples/api/README.md

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ The following example demonstrates steps how we describe and test our API using
44

55
### Step 1
66

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
99
error responses.
1010

1111
``` gherkin
@@ -31,24 +31,24 @@ Feature: get version
3131
And the response should match json:
3232
"""
3333
{
34-
"version": "v0.5.3"
34+
"version": "v0.0.0-dev"
3535
}
3636
"""
3737
```
3838

39-
Save it as **version.feature**.
39+
Save it as `features/version.feature`.
4040
Now we have described a success case and an error when the request method is not allowed.
4141

4242
### Step 2
4343

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
4545
steps are yet undefined and provide us with the snippets to implement them.
4646

4747
![Screenshot](https://raw.github.com/cucumber/godog/master/_examples/api/screenshots/undefined.png)
4848

4949
### Step 3
5050

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
5252
need to store state within steps (a response), we should introduce a structure with some variables.
5353

5454
``` go
@@ -74,6 +74,21 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) error {
7474
return godog.ErrPending
7575
}
7676

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+
7792
func InitializeScenario(s *godog.ScenarioContext) {
7893
api := &apiFeature{}
7994
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, api.iSendrequestTo)
@@ -84,19 +99,20 @@ func InitializeScenario(s *godog.ScenarioContext) {
8499

85100
### Step 4
86101

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:
88103

89104
``` go
90105
// file: api_test.go
91106
package main
92107

93108
import (
94109
"context"
95-
"bytes"
96110
"encoding/json"
97111
"fmt"
98112
"net/http"
99113
"net/http/httptest"
114+
"reflect"
115+
"testing"
100116

101117
"github.com/cucumber/godog"
102118
)
@@ -105,7 +121,7 @@ type apiFeature struct {
105121
resp *httptest.ResponseRecorder
106122
}
107123

108-
func (a *apiFeature) resetResponse(interface{}) {
124+
func (a *apiFeature) resetResponse(*godog.Scenario) {
109125
a.resp = httptest.NewRecorder()
110126
}
111127

@@ -141,48 +157,67 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
141157
return nil
142158
}
143159

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 {
148165
return
149166
}
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 {
151170
return
152171
}
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)
156176
}
157-
return
177+
return nil
158178
}
159179

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) {
161196
api := &apiFeature{}
162197

163198
ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
164199
api.resetResponse(sc)
165200
return ctx, nil
166201
})
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)
171205
}
172206
```
173207

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.
175210
If we made some mistakes in step implementations, we will know about it when we run the tests.
176211

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
178213
match their types and values.
179214

180215
In case if some router is used, you may search the handler based on the endpoint. Current example
181216
uses a standard http package.
182217

183218
### Step 5
184219

185-
Finally, lets implement the **api** server:
220+
Finally, lets implement the `API` server:
186221

187222
``` go
188223
// file: api.go
@@ -191,60 +226,52 @@ package main
191226

192227
import (
193228
"encoding/json"
194-
"fmt"
195229
"net/http"
196230

197231
"github.com/cucumber/godog"
198232
)
199233

200234
func getVersion(w http.ResponseWriter, r *http.Request) {
201-
if r.Method != "GET" {
235+
if r.Method != http.MethodGet {
202236
fail(w, "Method not allowed", http.StatusMethodNotAllowed)
203237
return
204238
}
239+
205240
data := struct {
206241
Version string `json:"version"`
207242
}{Version: godog.Version}
208243

209244
ok(w, data)
210245
}
211246

212-
func main() {
213-
http.HandleFunc("/version", getVersion)
214-
http.ListenAndServe(":8080", nil)
215-
}
216-
217247
// fail writes a json response with error msg and status header
218248
func fail(w http.ResponseWriter, msg string, status int) {
219-
w.Header().Set("Content-Type", "application/json")
249+
w.WriteHeader(status)
220250

221251
data := struct {
222252
Error string `json:"error"`
223253
}{Error: msg}
224-
225254
resp, _ := json.Marshal(data)
226-
w.WriteHeader(status)
227255

228-
fmt.Fprintf(w, string(resp))
256+
w.Header().Set("Content-Type", "application/json")
257+
w.Write(resp)
229258
}
230259

231260
// ok writes data to response with 200 status
232261
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-
240262
resp, err := json.Marshal(data)
241263
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)
244265
return
245266
}
246267

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)
248275
}
249276
```
250277

@@ -253,7 +280,7 @@ used to respond with the correct constant version number.
253280

254281
### Step 6
255282

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`
257284

258285
![Screenshot](https://raw.github.com/cucumber/godog/master/_examples/api/screenshots/passed.png)
259286

_examples/api/api.go

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,50 @@ package main
33

44
import (
55
"encoding/json"
6-
"fmt"
76
"net/http"
87

98
"github.com/cucumber/godog"
109
)
1110

1211
func getVersion(w http.ResponseWriter, r *http.Request) {
13-
if r.Method != "GET" {
12+
if r.Method != http.MethodGet {
1413
fail(w, "Method not allowed", http.StatusMethodNotAllowed)
1514
return
1615
}
16+
1717
data := struct {
1818
Version string `json:"version"`
1919
}{Version: godog.Version}
2020

2121
ok(w, data)
2222
}
2323

24-
func main() {
25-
http.HandleFunc("/version", getVersion)
26-
http.ListenAndServe(":8080", nil)
27-
}
28-
2924
// fail writes a json response with error msg and status header
3025
func fail(w http.ResponseWriter, msg string, status int) {
31-
w.Header().Set("Content-Type", "application/json")
26+
w.WriteHeader(status)
3227

3328
data := struct {
3429
Error string `json:"error"`
3530
}{Error: msg}
36-
3731
resp, _ := json.Marshal(data)
38-
w.WriteHeader(status)
3932

40-
fmt.Fprintf(w, string(resp))
33+
w.Header().Set("Content-Type", "application/json")
34+
w.Write(resp)
4135
}
4236

4337
// ok writes data to response with 200 status
4438
func ok(w http.ResponseWriter, data interface{}) {
45-
w.Header().Set("Content-Type", "application/json")
46-
47-
if s, ok := data.(string); ok {
48-
fmt.Fprintf(w, s)
49-
return
50-
}
51-
5239
resp, err := json.Marshal(data)
5340
if err != nil {
54-
w.WriteHeader(http.StatusInternalServerError)
55-
fail(w, "oops something evil has happened", 500)
41+
fail(w, "Oops something evil has happened", http.StatusInternalServerError)
5642
return
5743
}
5844

59-
fmt.Fprintf(w, string(resp))
45+
w.Header().Set("Content-Type", "application/json")
46+
w.Write(resp)
47+
}
48+
49+
func main() {
50+
http.HandleFunc("/version", getVersion)
51+
http.ListenAndServe(":8080", nil)
6052
}

_examples/api/api_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"reflect"
10+
"testing"
1011

1112
"github.com/cucumber/godog"
1213
)
@@ -71,6 +72,21 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) (err erro
7172
return nil
7273
}
7374

75+
func TestFeatures(t *testing.T) {
76+
suite := godog.TestSuite{
77+
ScenarioInitializer: InitializeScenario,
78+
Options: &godog.Options{
79+
Format: "pretty",
80+
Paths: []string{"features"},
81+
TestingT: t, // Testing instance that will run subtests.
82+
},
83+
}
84+
85+
if suite.Run() != 0 {
86+
t.Fatal("non-zero status returned, failed to run feature tests")
87+
}
88+
}
89+
7490
func InitializeScenario(ctx *godog.ScenarioContext) {
7591
api := &apiFeature{}
7692

_examples/api/screenshots/passed.png

-35.3 KB
Loading
-41.1 KB
Loading

0 commit comments

Comments
 (0)