├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc.go ├── doc_test.go ├── example └── main.go ├── expect.go ├── frisby.gif ├── frisby.go ├── glide.lock ├── glide.yaml └── global.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | vendor 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | notifications: 4 | email: false 5 | 6 | branches: 7 | only: 8 | - master 9 | - develop 10 | 11 | install: 12 | - go get -u github.com/bitly/go-simplejson 13 | - go get -u github.com/mozillazg/request 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tony Worm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frisby 2 | 3 | [![Build Status](https://travis-ci.org/verdverm/frisby.svg?branch=master)](https://travis-ci.org/verdverm/frisby) 4 | [![GoDoc](https://godoc.org/github.com/verdverm/frisby?status.svg)](https://godoc.org/github.com/verdverm/frisby) 5 | [![GitHub release](https://img.shields.io/github/release/qubyte/rubidium.svg)](https://github.com/verdverm/frisby) 6 | 7 | REST API testing framework inspired by frisby-js, written in Go 8 | ### Proposals 9 | 10 | I'm starting to work on `frisby` again with the following ideas: 11 | 12 | 1. Read specification files 13 | - pyresttest 14 | - frisby.js 15 | - swagger spec 16 | - other? 17 | 1. Use as a load tester 18 | - like Locust.io 19 | - distributed 20 | 1. UI 21 | - Dashboard 22 | - Analytics 23 | - Reports 24 | - Manage multiple instances 25 | 2. Backend 26 | - master/minions 27 | - db for analytics 28 | - api for UI / clients [Goa](http://goa.domain) 29 | - federation of minion groups? 30 | 31 | Please comment on any issues or PRs related to these proposals. 32 | If you don't see an issue, PR, or idea; definitely add it! 33 | 34 | 35 | ### Installation 36 | 37 | ```shell 38 | go get -u github.com/verdverm/frisby 39 | ``` 40 | 41 | ### Basic Usage 42 | 43 | First create a Frisby object: 44 | 45 | ```go 46 | // create an object with a given name (used in the report) 47 | F := frisby.Create("Test successful user login"). 48 | Get("https://golang.org") 49 | ``` 50 | 51 | Add any pre-flight data 52 | 53 | ```go 54 | F.SetHeader("Content-Type": "application/json"). 55 | SetHeader("Accept", "application/json, text/plain, */*"). 56 | SetJson([]string{"item1", "item2", "item3"}) 57 | ``` 58 | 59 | There is also a Global object for setting repeated Pre-flight options. 60 | 61 | ```go 62 | frisby.Global.BasicAuth("username", "password"). 63 | SetHeader("Authorization", "Bearer " + TOKEN) 64 | ``` 65 | 66 | Next send the request: 67 | 68 | ```go 69 | F.Send() 70 | ``` 71 | 72 | Then assert and inspect the response: 73 | 74 | ```go 75 | F.ExpectStatus(200). 76 | ExpectJson("nested.path.to.value", "sometext"). 77 | ExpectJson("nested.path.to.object", golangObject). 78 | ExpectJson("nested.array.7.id", 23). 79 | ExpectJsonLength("nested.array", 8). 80 | AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) { 81 | val, _ := json.Get("proxy").String() 82 | frisby.Global.SetProxy(val) 83 | }) 84 | ``` 85 | 86 | Finally, print out a report of the tests 87 | 88 | ```go 89 | frisby.Global.PrintReport() 90 | ``` 91 | 92 | Check any error(s), however the global report prints any that occured as well 93 | 94 | `err := F.Error()` 95 | 96 | ```go 97 | errs := F.Errors() 98 | for _,e := range errs { 99 | fmt.Println("Error: ", e) 100 | } 101 | ``` 102 | 103 | 104 | ### HTTP Method functions 105 | 106 | Your basic HTTP verbs: 107 | 108 | * Get(url string) 109 | * Post(url string) 110 | * Put(url string) 111 | * Patch(url string) 112 | * Delete(url string) 113 | * Head(url string) 114 | * Options(url string) 115 | 116 | ### Pre-flight functions 117 | 118 | Functions called before `Send()` 119 | 120 | You can also set theses on the `frisby.Global` object for persisting state over multiple requests. 121 | 122 | ( Most of these come from [github.com/mozillazg/request](https://github.com/mozillazg/request)) 123 | 124 | * BasicAuth(username,password string) 125 | * Proxy(url string) 126 | * SetHeader(key,value string) 127 | * SetHeaders(map[string]string) 128 | * SetCookies(key,value string) 129 | * SetCookiess(map[string]string) 130 | * SetDate(key,value string) 131 | * SetDates(map[string]string) 132 | * SetParam(key,value string) 133 | * SetParams(map[string]string) 134 | * SetJson(interface{}) 135 | * SetFile(filename string) 136 | 137 | 138 | ### Post-flight functions 139 | 140 | Functions called after `Send()` 141 | 142 | * ExpectStatus(code int) 143 | * ExpectHeader(key, value string) 144 | * ExpectContent(content string) 145 | * ExpectJson(path string, value interface{}) 146 | * ExpectJsonLength(path string, length int) 147 | * ExpectJsonType(path string, value_type reflect.Kind) 148 | * AfterContent( func(Frisby,[]byte,error) ) 149 | * AfterText( func(Frisby,string,error) ) 150 | * AfterJson( func(Frisby,simplejson.Json,error) ) 151 | * PauseTest(t time.Duration) 152 | * PrintBody() 153 | * PrintReport() 154 | * PrintGoTestReport() 155 | 156 | 157 | ### More examples 158 | 159 | You can find a longer example [here](https://github.com/verdverm/pomopomo/tree/master/test/api) 160 | 161 | ```go 162 | package main 163 | 164 | import ( 165 | "fmt" 166 | "reflect" 167 | 168 | "github.com/bitly/go-simplejson" 169 | "github.com/verdverm/frisby" 170 | ) 171 | 172 | func main() { 173 | fmt.Println("Frisby!\n") 174 | 175 | frisby.Create("Test GET Go homepage"). 176 | Get("http://golang.org"). 177 | Send(). 178 | ExpectStatus(200). 179 | ExpectContent("The Go Programming Language") 180 | 181 | frisby.Create("Test GET Go homepage (which fails)"). 182 | Get("http://golang.org"). 183 | Send(). 184 | ExpectStatus(400). 185 | ExpectContent("A string which won't be found") 186 | 187 | frisby.Create("Test POST"). 188 | Post("http://httpbin.org/post"). 189 | SetData("test_key", "test_value"). 190 | Send(). 191 | ExpectStatus(200) 192 | 193 | frisby.Create("Test ExpectJsonType"). 194 | Post("http://httpbin.org/post"). 195 | Send(). 196 | ExpectStatus(200). 197 | ExpectJsonType("url", reflect.String) 198 | 199 | frisby.Create("Test ExpectJson"). 200 | Post("http://httpbin.org/post"). 201 | Send(). 202 | ExpectStatus(200). 203 | ExpectJson("url", "http://httpbin.org/post"). 204 | ExpectJson("headers.Accept", "*/*") 205 | 206 | frisby.Create("Test ExpectJsonLength (which fails)"). 207 | Post("http://httpbin.org/post"). 208 | SetJson([]string{"item1", "item2", "item3"}). 209 | Send(). 210 | ExpectStatus(200). 211 | ExpectJsonLength("json", 4) 212 | 213 | frisby.Create("Test AfterJson"). 214 | Post("http://httpbin.org/post"). 215 | Send(). 216 | ExpectStatus(200). 217 | AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) { 218 | val, _ := json.Get("url").String() 219 | frisby.Global.SetProxy(val) 220 | }) 221 | 222 | frisby.Global.PrintReport() 223 | } 224 | 225 | ``` 226 | 227 | Sample Output 228 | 229 | ``` 230 | Frisby! 231 | 232 | ....... 233 | For 7 requests made 234 | FAILED [3/13] 235 | [Test ExpectJsonLength] 236 | - Expect length to be 4, but got 3 237 | [Test GET Go homepage (which fails)] 238 | - Expected Status 400, but got 200: "200 OK" 239 | - Expected Body to contain "A string which won't be found", but it was missing 240 | ``` 241 | 242 | ![catch!](https://raw.github.com/verdverm/frisby/master/frisby.gif) 243 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // frisby is an API testing framework, written in Go, inspired by frisby-js 2 | 3 | package frisby 4 | -------------------------------------------------------------------------------- /doc_test.go: -------------------------------------------------------------------------------- 1 | package frisby_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/bitly/go-simplejson" 8 | "github.com/verdverm/frisby" 9 | ) 10 | 11 | func init() { 12 | frisby.Global.PrintProgressDot = false 13 | } 14 | 15 | func ExampleFrisby_Get() { 16 | frisby.Create("Test GET Go homepage"). 17 | Get("http://golang.org"). 18 | Send(). 19 | ExpectStatus(200). 20 | ExpectContent("The Go Programming Language"). 21 | PrintReport() 22 | 23 | // Output: Pass [Test GET Go homepage] 24 | } 25 | 26 | func ExampleFrisby_Post() { 27 | frisby.Create("Test POST"). 28 | Post("http://httpbin.org/post"). 29 | SetData("test_key", "test_value"). 30 | Send(). 31 | ExpectStatus(200). 32 | PrintReport() 33 | 34 | // Output: Pass [Test POST] 35 | } 36 | 37 | func ExampleFrisby_PrintReport() { 38 | frisby.Create("Test GET Go homepage"). 39 | Get("http://golang.org"). 40 | Send(). 41 | ExpectStatus(400). 42 | ExpectContent("A string which won't be found"). 43 | AddError("Manually Added Error"). 44 | PrintReport() 45 | 46 | // Output: FAIL [Test GET Go homepage] 47 | // - Expected Status 400, but got 200: "200 OK" 48 | // - Expected Body to contain "A string which won't be found", but it was missing 49 | // - Manually Added Error 50 | } 51 | 52 | func ExampleFrisby_ExpectJsonType() { 53 | frisby.Create("Test ExpectJsonType"). 54 | Post("http://httpbin.org/post"). 55 | Send(). 56 | ExpectStatus(200). 57 | ExpectJsonType("url", reflect.String). 58 | PrintReport() 59 | 60 | // Output: Pass [Test ExpectJsonType] 61 | } 62 | 63 | func ExampleFrisby_ExpectJson() { 64 | frisby.Create("Test ExpectJson"). 65 | Post("http://httpbin.org/post"). 66 | Send(). 67 | ExpectStatus(200). 68 | ExpectJson("url", "http://httpbin.org/post"). 69 | ExpectJson("headers.Accept", "*/*"). 70 | PrintReport() 71 | 72 | // Output: Pass [Test ExpectJson] 73 | } 74 | 75 | func ExampleFrisby_AfterJson() { 76 | frisby.Create("Test AfterJson"). 77 | Post("http://httpbin.org/post"). 78 | Send(). 79 | ExpectStatus(200). 80 | AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) { 81 | val, _ := json.Get("url").String() 82 | fmt.Println("url =", val) 83 | }) 84 | 85 | // Output: url = http://httpbin.org/post 86 | } 87 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/bitly/go-simplejson" 8 | "github.com/verdverm/frisby" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("Frisby!\n") 13 | 14 | frisby.Create("Test GET Go homepage"). 15 | Get("http://golang.org"). 16 | Send(). 17 | ExpectStatus(200). 18 | ExpectContent("The Go Programming Language") 19 | 20 | frisby.Create("Test GET Go homepage (which fails)"). 21 | Get("http://golang.org"). 22 | Send(). 23 | ExpectStatus(400). 24 | ExpectContent("A string which won't be found") 25 | 26 | frisby.Create("Test POST"). 27 | Post("http://httpbin.org/post"). 28 | SetData("test_key", "test_value"). 29 | Send(). 30 | ExpectStatus(200) 31 | 32 | frisby.Create("Test ExpectJsonType"). 33 | Post("http://httpbin.org/post"). 34 | Send(). 35 | ExpectStatus(200). 36 | ExpectJsonType("url", reflect.String) 37 | 38 | frisby.Create("Test ExpectJson"). 39 | Post("http://httpbin.org/post"). 40 | Send(). 41 | ExpectStatus(200). 42 | ExpectJson("url", "http://httpbin.org/post"). 43 | ExpectJson("headers.Accept", "*/*") 44 | 45 | frisby.Create("Test ExpectJsonLength"). 46 | Post("http://httpbin.org/post"). 47 | SetJson([]string{"item1", "item2", "item3"}). 48 | Send(). 49 | ExpectStatus(200). 50 | ExpectJsonLength("json", 3) 51 | 52 | frisby.Create("Test AfterJson"). 53 | Post("http://httpbin.org/post"). 54 | Send(). 55 | ExpectStatus(200). 56 | AfterJson(func(F *frisby.Frisby, json *simplejson.Json, err error) { 57 | val, _ := json.Get("url").String() 58 | frisby.Global.SetProxy(val) 59 | }) 60 | 61 | frisby.Global.PrintReport() 62 | } 63 | -------------------------------------------------------------------------------- /expect.go: -------------------------------------------------------------------------------- 1 | package frisby 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/bitly/go-simplejson" 10 | ) 11 | 12 | // ExpectFunc function type used as argument to Expect() 13 | type ExpectFunc func(F *Frisby) (bool, string) 14 | 15 | // Expect Checks according to the given function, which allows you to describe any kind of assertion. 16 | func (F *Frisby) Expect(foo ExpectFunc) *Frisby { 17 | Global.NumAsserts++ 18 | if ok, err_str := foo(F); !ok { 19 | F.AddError(err_str) 20 | } 21 | return F 22 | } 23 | 24 | // Checks the response status code 25 | func (F *Frisby) ExpectStatus(code int) *Frisby { 26 | Global.NumAsserts++ 27 | status := F.Resp.StatusCode 28 | if status != code { 29 | err_str := fmt.Sprintf("Expected Status %d, but got %d: %q", code, status, F.Resp.Status) 30 | F.AddError(err_str) 31 | } 32 | return F 33 | } 34 | 35 | // Checks for header and if values match 36 | func (F *Frisby) ExpectHeader(key, value string) *Frisby { 37 | Global.NumAsserts++ 38 | chk_val := F.Resp.Header.Get(key) 39 | if chk_val == "" { 40 | err_str := fmt.Sprintf("Expected Header %q, but it was missing", key) 41 | F.AddError(err_str) 42 | } else if chk_val != value { 43 | err_str := fmt.Sprintf("Expected Header %q to be %q, but got %q", key, value, chk_val) 44 | F.AddError(err_str) 45 | } 46 | return F 47 | } 48 | 49 | // Checks the response body for the given string 50 | func (F *Frisby) ExpectContent(content string) *Frisby { 51 | Global.NumAsserts++ 52 | text, err := F.Resp.Text() 53 | if err != nil { 54 | F.AddError(err.Error()) 55 | return F 56 | } 57 | contains := strings.Contains(text, content) 58 | if !contains { 59 | err_str := fmt.Sprintf("Expected Body to contain %q, but it was missing", content) 60 | F.AddError(err_str) 61 | } 62 | return F 63 | } 64 | 65 | // ExpectJson uses the reflect.DeepEqual to compare the response 66 | // JSON and the supplied JSON for structural and value equality 67 | // 68 | // path can be a dot joined field names. 69 | // ex: 'path.to.subobject.field' 70 | func (F *Frisby) ExpectJson(path string, value interface{}) *Frisby { 71 | Global.NumAsserts++ 72 | simp_json, err := F.Resp.Json() 73 | if err != nil { 74 | F.AddError(err.Error()) 75 | return F 76 | } 77 | 78 | if path != "" { 79 | // Loop over each path item and progress down the json path. 80 | path_items := strings.Split(path, Global.PathSeparator) 81 | for _, segment := range path_items { 82 | processed := false 83 | // If the path segment is an integer, and we're at an array, access the index. 84 | index, err := strconv.Atoi(segment) 85 | if err == nil { 86 | if _, err := simp_json.Array(); err == nil { 87 | simp_json = simp_json.GetIndex(index) 88 | processed = true 89 | } 90 | } 91 | 92 | if !processed { 93 | simp_json = simp_json.Get(segment) 94 | } 95 | } 96 | } 97 | json := simp_json.Interface() 98 | 99 | equal := false 100 | switch reflect.ValueOf(value).Kind() { 101 | case reflect.Int: 102 | val, err := simp_json.Int() 103 | if err != nil { 104 | F.AddError(err.Error()) 105 | return F 106 | } else { 107 | equal = (val == value.(int)) 108 | } 109 | case reflect.Float64: 110 | val, err := simp_json.Float64() 111 | if err != nil { 112 | F.AddError(err.Error()) 113 | return F 114 | } else { 115 | equal = (val == value.(float64)) 116 | } 117 | default: 118 | equal = reflect.DeepEqual(value, json) 119 | } 120 | 121 | if !equal { 122 | err_str := fmt.Sprintf("ExpectJson equality test failed for %q, got value: %v", path, json) 123 | F.AddError(err_str) 124 | } 125 | 126 | return F 127 | } 128 | 129 | // ExpectJsonType checks if the types of the response 130 | // JSON and the supplied JSON match 131 | // 132 | // path can be a dot joined field names. 133 | // ex: 'path.to.subobject.field' 134 | func (F *Frisby) ExpectJsonType(path string, val_type reflect.Kind) *Frisby { 135 | Global.NumAsserts++ 136 | json, err := F.Resp.Json() 137 | if err != nil { 138 | F.AddError(err.Error()) 139 | return F 140 | } 141 | 142 | if path != "" { 143 | path_items := strings.Split(path, Global.PathSeparator) 144 | json = json.GetPath(path_items...) 145 | } 146 | 147 | json_json := json.Interface() 148 | 149 | json_val := reflect.ValueOf(json_json) 150 | if val_type != json_val.Kind() { 151 | err_str := fmt.Sprintf("Expect Json %q type to be %q, but got %T", path, val_type, json_json) 152 | F.AddError(err_str) 153 | } 154 | 155 | return F 156 | } 157 | 158 | // ExpectJsonLength checks if the JSON at path 159 | // is an array and has the correct length 160 | // 161 | // path can be a dot joined field names. 162 | // ex: 'path.to.subobject.field' 163 | func (F *Frisby) ExpectJsonLength(path string, length int) *Frisby { 164 | Global.NumAsserts++ 165 | json, err := F.Resp.Json() 166 | if err != nil { 167 | F.AddError(err.Error()) 168 | return F 169 | } 170 | 171 | if path != "" { 172 | path_items := strings.Split(path, Global.PathSeparator) 173 | json = json.GetPath(path_items...) 174 | } 175 | 176 | ary, err := json.Array() 177 | if err != nil { 178 | F.AddError(err.Error()) 179 | return F 180 | } 181 | L := len(ary) 182 | 183 | if L != length { 184 | err_str := fmt.Sprintf("Expect length to be %d, but got %d", length, L) 185 | F.AddError(err_str) 186 | } 187 | 188 | return F 189 | } 190 | 191 | // function type used as argument to AfterContent() 192 | type AfterContentFunc func(F *Frisby, content []byte, err error) 193 | 194 | // AfterContent allows you to write your own functions for inspecting the body of the response. 195 | // You are also provided with the Frisby object. 196 | // 197 | // The function signiture is AfterContentFunc 198 | // type AfterContentFunc func(F *Frisby, content []byte, err error) 199 | // 200 | func (F *Frisby) AfterContent(foo AfterContentFunc) *Frisby { 201 | content, err := F.Resp.Content() 202 | foo(F, content, err) 203 | return F 204 | } 205 | 206 | // function type used as argument to AfterText() 207 | type AfterTextFunc func(F *Frisby, text string, err error) 208 | 209 | // AfterText allows you to write your own functions for inspecting the body of the response. 210 | // You are also provided with the Frisby object. 211 | // 212 | // The function signiture is AfterTextFunc 213 | // type AfterTextFunc func(F *Frisby, text string, err error) 214 | // 215 | func (F *Frisby) AfterText(foo AfterTextFunc) *Frisby { 216 | text, err := F.Resp.Text() 217 | foo(F, text, err) 218 | return F 219 | } 220 | 221 | // function type used as argument to AfterJson() 222 | type AfterJsonFunc func(F *Frisby, json *simplejson.Json, err error) 223 | 224 | // AfterJson allows you to write your own functions for inspecting the body of the response. 225 | // You are also provided with the Frisby object. 226 | // 227 | // The function signiture is AfterJsonFunc 228 | // type AfterJsonFunc func(F *Frisby, json *simplejson.Json, err error) 229 | // 230 | // simplejson docs: https://github.com/bitly/go-simplejson 231 | func (F *Frisby) AfterJson(foo AfterJsonFunc) *Frisby { 232 | json, err := F.Resp.Json() 233 | foo(F, json, err) 234 | return F 235 | } 236 | 237 | // Prints the body of the response 238 | func (F *Frisby) PrintBody() *Frisby { 239 | str, err := F.Resp.Text() 240 | if err != nil { 241 | F.AddError(err.Error()) 242 | return F 243 | } 244 | fmt.Println(str) 245 | return F 246 | } 247 | 248 | // Prints a report for the Frisby Object 249 | // 250 | // If there are any errors, they will all be printed as well 251 | func (F *Frisby) PrintReport() *Frisby { 252 | if len(F.Errs) == 0 { 253 | fmt.Printf("Pass [%s]\n", F.Name) 254 | } else { 255 | fmt.Printf("FAIL [%s]\n", F.Name) 256 | for _, e := range F.Errs { 257 | fmt.Println(" - ", e) 258 | } 259 | } 260 | 261 | return F 262 | } 263 | 264 | // Prints a report for the Frisby Object in go_test format 265 | // 266 | // If there are any errors, they will all be printed as well 267 | func (F *Frisby) PrintGoTestReport() *Frisby { 268 | if len(F.Errs) == 0 { 269 | fmt.Printf("=== RUN %s\n--- PASS: %s (%.2fs)\n", F.Name, F.Name, F.ExecutionTime) 270 | } else { 271 | fmt.Printf("=== RUN %s\n--- FAIL: %s (%.2fs)\n", F.Name, F.Name, F.ExecutionTime) 272 | for _, e := range F.Errs { 273 | fmt.Println(" ", e) 274 | } 275 | } 276 | return F 277 | } 278 | -------------------------------------------------------------------------------- /frisby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hofstadter-io/frisby/b16556248a9a21a8166936e49771527189661abe/frisby.gif -------------------------------------------------------------------------------- /frisby.go: -------------------------------------------------------------------------------- 1 | package frisby 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | "github.com/mozillazg/request" 12 | ) 13 | 14 | var Global global_data 15 | 16 | const defaultFileKey = "file" 17 | 18 | type Frisby struct { 19 | Name string 20 | Url string 21 | Method string 22 | 23 | Req *request.Request 24 | Resp *request.Response 25 | Errs []error 26 | ExecutionTime float64 27 | } 28 | 29 | // Creates a new Frisby object with the given name. 30 | // 31 | // The given name will be used if you call PrintReport() 32 | func Create(name string) *Frisby { 33 | F := new(Frisby) 34 | F.Name = name 35 | F.Req = request.NewRequest(new(http.Client)) 36 | F.Errs = make([]error, 0) 37 | 38 | // copy in global settings 39 | F.Req.BasicAuth = Global.Req.BasicAuth 40 | F.Req.Proxy = Global.Req.Proxy 41 | F.SetHeaders(Global.Req.Headers) 42 | F.SetCookies(Global.Req.Cookies) 43 | F.SetDatas(Global.Req.Data) 44 | F.SetParams(Global.Req.Params) 45 | F.Req.Json = Global.Req.Json 46 | F.Req.Files = append(F.Req.Files, Global.Req.Files...) 47 | 48 | // initialize request 49 | F.Req.Params = make(map[string]string) 50 | 51 | return F 52 | } 53 | 54 | // Set the HTTP method to GET for the given URL 55 | func (F *Frisby) Get(url string) *Frisby { 56 | F.Method = "GET" 57 | F.Url = url 58 | return F 59 | } 60 | 61 | // Set the HTTP method to POST for the given URL 62 | func (F *Frisby) Post(url string) *Frisby { 63 | F.Method = "POST" 64 | F.Url = url 65 | return F 66 | } 67 | 68 | // Set the HTTP method to PUT for the given URL 69 | func (F *Frisby) Put(url string) *Frisby { 70 | F.Method = "PUT" 71 | F.Url = url 72 | return F 73 | } 74 | 75 | // Set the HTTP method to PATCH for the given URL 76 | func (F *Frisby) Patch(url string) *Frisby { 77 | F.Method = "PATCH" 78 | F.Url = url 79 | return F 80 | } 81 | 82 | // Set the HTTP method to DELETE for the given URL 83 | func (F *Frisby) Delete(url string) *Frisby { 84 | F.Method = "DELETE" 85 | F.Url = url 86 | return F 87 | } 88 | 89 | // Set the HTTP method to HEAD for the given URL 90 | func (F *Frisby) Head(url string) *Frisby { 91 | F.Method = "HEAD" 92 | F.Url = url 93 | return F 94 | } 95 | 96 | // Set the HTTP method to OPTIONS for the given URL 97 | func (F *Frisby) Options(url string) *Frisby { 98 | F.Method = "OPTIONS" 99 | F.Url = url 100 | return F 101 | } 102 | 103 | // Set BasicAuth values for the coming request 104 | func (F *Frisby) BasicAuth(user, passwd string) *Frisby { 105 | F.Req.BasicAuth = request.BasicAuth{user, passwd} 106 | return F 107 | } 108 | 109 | // Set Proxy URL for the coming request 110 | func (F *Frisby) SetProxy(url string) *Frisby { 111 | F.Req.Proxy = url 112 | return F 113 | } 114 | 115 | // Set a Header value for the coming request 116 | func (F *Frisby) SetHeader(key, value string) *Frisby { 117 | if F.Req.Headers == nil { 118 | F.Req.Headers = make(map[string]string) 119 | } 120 | F.Req.Headers[key] = value 121 | return F 122 | } 123 | 124 | // Set several Headers for the coming request 125 | func (F *Frisby) SetHeaders(headers map[string]string) *Frisby { 126 | if F.Req.Headers == nil { 127 | F.Req.Headers = make(map[string]string) 128 | } 129 | for key, value := range headers { 130 | F.Req.Headers[key] = value 131 | } 132 | return F 133 | } 134 | 135 | // Set a Cookie value for the coming request 136 | func (F *Frisby) SetCookie(key, value string) *Frisby { 137 | if F.Req.Cookies == nil { 138 | F.Req.Cookies = make(map[string]string) 139 | } 140 | F.Req.Cookies[key] = value 141 | return F 142 | } 143 | 144 | // Set several Cookie values for the coming request 145 | func (F *Frisby) SetCookies(cookies map[string]string) *Frisby { 146 | if F.Req.Cookies == nil { 147 | F.Req.Cookies = make(map[string]string) 148 | } 149 | for key, value := range cookies { 150 | F.Req.Cookies[key] = value 151 | } 152 | return F 153 | } 154 | 155 | // Set a Form data for the coming request 156 | func (F *Frisby) SetData(key, value string) *Frisby { 157 | if F.Req.Data == nil { 158 | F.Req.Data = make(map[string]string) 159 | } 160 | F.Req.Data[key] = value 161 | return F 162 | } 163 | 164 | // Set several Form data for the coming request 165 | func (F *Frisby) SetDatas(datas map[string]string) *Frisby { 166 | if F.Req.Data == nil { 167 | F.Req.Data = make(map[string]string) 168 | } 169 | for key, value := range datas { 170 | F.Req.Data[key] = value 171 | } 172 | return F 173 | } 174 | 175 | // Set a url Param for the coming request 176 | func (F *Frisby) SetParam(key, value string) *Frisby { 177 | if F.Req.Params == nil { 178 | F.Req.Params = make(map[string]string) 179 | } 180 | F.Req.Params[key] = value 181 | return F 182 | } 183 | 184 | // Set several url Param for the coming request 185 | func (F *Frisby) SetParams(params map[string]string) *Frisby { 186 | if F.Req.Params == nil { 187 | F.Req.Params = make(map[string]string) 188 | } 189 | for key, value := range params { 190 | F.Req.Params[key] = value 191 | } 192 | return F 193 | } 194 | 195 | // Set the JSON body for the coming request 196 | func (F *Frisby) SetJson(json interface{}) *Frisby { 197 | F.Req.Json = json 198 | return F 199 | } 200 | 201 | // Add a file to the Form data for the coming request 202 | func (F *Frisby) AddFile(filename string) *Frisby { 203 | file, err := os.Open(filename) 204 | if err != nil { 205 | F.Errs = append(F.Errs, err) 206 | } else { 207 | fileField := request.FileField{ 208 | FieldName: defaultFileKey, 209 | FileName: filepath.Base(filename), 210 | File: file} 211 | F.Req.Files = append(F.Req.Files, fileField) 212 | } 213 | return F 214 | } 215 | 216 | // Add a file to the Form data for the coming request 217 | func (F *Frisby) AddFileByKey(key, filename string) *Frisby { 218 | file, err := os.Open(filename) 219 | if err != nil { 220 | F.Errs = append(F.Errs, err) 221 | } else { 222 | if len(key) == 0 { 223 | key = defaultFileKey 224 | } 225 | fileField := request.FileField{ 226 | FieldName: key, 227 | FileName: filepath.Base(filename), 228 | File: file} 229 | F.Req.Files = append(F.Req.Files, fileField) 230 | } 231 | return F 232 | } 233 | 234 | // Send the actual request to the URL 235 | func (F *Frisby) Send() *Frisby { 236 | Global.NumRequest++ 237 | if Global.PrintProgressName { 238 | fmt.Println(F.Name) 239 | } else if Global.PrintProgressDot { 240 | fmt.Printf("") 241 | } 242 | 243 | start := time.Now() 244 | 245 | var err error 246 | switch F.Method { 247 | case "GET": 248 | F.Resp, err = F.Req.Get(F.Url) 249 | case "POST": 250 | F.Resp, err = F.Req.Post(F.Url) 251 | case "PUT": 252 | F.Resp, err = F.Req.Put(F.Url) 253 | case "PATCH": 254 | F.Resp, err = F.Req.Patch(F.Url) 255 | case "DELETE": 256 | F.Resp, err = F.Req.Delete(F.Url) 257 | case "HEAD": 258 | F.Resp, err = F.Req.Head(F.Url) 259 | case "OPTIONS": 260 | F.Resp, err = F.Req.Options(F.Url) 261 | } 262 | 263 | F.ExecutionTime = time.Since(start).Seconds() 264 | 265 | if err != nil { 266 | F.Errs = append(F.Errs, err) 267 | } 268 | 269 | return F 270 | } 271 | 272 | // Manually add an error, if you need to 273 | func (F *Frisby) AddError(err_str string) *Frisby { 274 | err := errors.New(err_str) 275 | F.Errs = append(F.Errs, err) 276 | Global.AddError(F.Name, err_str) 277 | return F 278 | } 279 | 280 | // Get the most recent error for the Frisby object 281 | // 282 | // This function should be called last 283 | func (F *Frisby) Error() error { 284 | if len(F.Errs) > 0 { 285 | return F.Errs[len(F.Errs)-1] 286 | } 287 | return nil 288 | } 289 | 290 | // Get all errors for the Frisby object 291 | // 292 | // This function should be called last 293 | func (F *Frisby) Errors() []error { 294 | return F.Errs 295 | } 296 | 297 | // Pause your testrun for a defined amount of seconds 298 | func (F *Frisby) PauseTest(t time.Duration) *Frisby { 299 | time.Sleep(t * time.Second) 300 | return nil 301 | } 302 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 0d97a97f7ae077155410f0b0d7fa9e8c97db0a534364b3e37cb412b18c5065e2 2 | updated: 2016-06-30T18:57:24.896487813-06:00 3 | imports: 4 | - name: github.com/bitly/go-simplejson 5 | version: aabad6e819789e569bd6aabf444c935aa9ba1e44 6 | - name: github.com/mozillazg/request 7 | version: 82f729b79d90a2a46e2e7f71bb302afdf65fdbb1 8 | - name: github.com/verdverm/frisby 9 | version: 9c740cc78802630b658e308758c27571ad8322e3 10 | - name: golang.org/x/net 11 | version: c4c3ea71919de159c9e246d7be66deb7f0a39a58 12 | subpackages: 13 | - proxy 14 | - publicsuffix 15 | devImports: [] 16 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/bitly/go-simplejson 4 | - package: github.com/mozillazg/request 5 | - package: github.com/verdverm/frisby 6 | -------------------------------------------------------------------------------- /global.go: -------------------------------------------------------------------------------- 1 | package frisby 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/mozillazg/request" 10 | ) 11 | 12 | type global_data struct { 13 | Req *request.Request 14 | Errs map[string][]error 15 | 16 | NumRequest int 17 | NumAsserts int 18 | NumErrored int 19 | 20 | PrintProgressName bool 21 | PrintProgressDot bool 22 | 23 | PathSeparator string 24 | } 25 | 26 | const DefaultPathSeparator = "." 27 | 28 | func init() { 29 | Global.Req = request.NewRequest(new(http.Client)) 30 | Global.Errs = make(map[string][]error, 0) 31 | Global.PrintProgressDot = true 32 | Global.PathSeparator = DefaultPathSeparator 33 | } 34 | 35 | // Set BasicAuth values for the coming request 36 | func (G *global_data) BasicAuth(user, passwd string) *global_data { 37 | G.Req.BasicAuth = request.BasicAuth{user, passwd} 38 | return G 39 | } 40 | 41 | // Set Proxy URL for the coming request 42 | func (G *global_data) SetProxy(url string) *global_data { 43 | G.Req.Proxy = url 44 | return G 45 | } 46 | 47 | // Set a Header value for the coming request 48 | func (G *global_data) SetHeader(key, value string) *global_data { 49 | if G.Req.Headers == nil { 50 | G.Req.Headers = make(map[string]string) 51 | } 52 | G.Req.Headers[key] = value 53 | return G 54 | } 55 | 56 | // Set several Headers for the coming request 57 | func (G *global_data) SetHeaders(headers map[string]string) *global_data { 58 | if G.Req.Headers == nil { 59 | G.Req.Headers = make(map[string]string) 60 | } 61 | for key, value := range headers { 62 | G.Req.Headers[key] = value 63 | } 64 | return G 65 | } 66 | 67 | // Set a Cookie value for the coming request 68 | func (G *global_data) SetCookie(key, value string) *global_data { 69 | if G.Req.Cookies == nil { 70 | G.Req.Cookies = make(map[string]string) 71 | } 72 | G.Req.Cookies[key] = value 73 | return G 74 | } 75 | 76 | // Set several Cookie values for the coming request 77 | func (G *global_data) SetCookies(cookies map[string]string) *global_data { 78 | if G.Req.Cookies == nil { 79 | G.Req.Cookies = make(map[string]string) 80 | } 81 | for key, value := range cookies { 82 | G.Req.Cookies[key] = value 83 | } 84 | return G 85 | } 86 | 87 | // Set a Gorm data for the coming request 88 | func (G *global_data) SetData(key, value string) *global_data { 89 | if G.Req.Data == nil { 90 | G.Req.Data = make(map[string]string) 91 | } 92 | G.Req.Data[key] = value 93 | return G 94 | } 95 | 96 | // Set several Gorm data for the coming request 97 | func (G *global_data) SetDatas(datas map[string]string) *global_data { 98 | if G.Req.Data == nil { 99 | G.Req.Data = make(map[string]string) 100 | } 101 | for key, value := range datas { 102 | G.Req.Data[key] = value 103 | } 104 | return G 105 | } 106 | 107 | // Set a url Param for the coming request 108 | func (G *global_data) SetParam(key, value string) *global_data { 109 | if G.Req.Params == nil { 110 | G.Req.Params = make(map[string]string) 111 | } 112 | G.Req.Params[key] = value 113 | return G 114 | } 115 | 116 | // Set several url Param for the coming request 117 | func (G *global_data) SetParams(params map[string]string) *global_data { 118 | if G.Req.Params == nil { 119 | G.Req.Params = make(map[string]string) 120 | } 121 | for key, value := range params { 122 | G.Req.Params[key] = value 123 | } 124 | return G 125 | } 126 | 127 | // Set the JSON body for the coming request 128 | func (G *global_data) SetJson(json interface{}) *global_data { 129 | G.Req.Json = json 130 | return G 131 | } 132 | 133 | // Add a file to the Gorm data for the coming request 134 | func (G *global_data) AddFile(filename string) *global_data { 135 | file, err := os.Open(filename) 136 | if err != nil { 137 | G.AddError("Global", err.Error()) 138 | fmt.Println("Error adding file to global") 139 | } else { 140 | fileField := request.FileField{"file", filename, file} 141 | G.Req.Files = append(G.Req.Files, fileField) 142 | } 143 | return G 144 | } 145 | 146 | // Manually add an error, if you need to 147 | func (G *global_data) AddError(name, err_str string) *global_data { 148 | G.NumErrored++ 149 | err := errors.New(err_str) 150 | G.Errs[name] = append(G.Errs[name], err) 151 | return G 152 | } 153 | 154 | // Get all errors for the global_data object 155 | // 156 | // This function should be called last 157 | func (G *global_data) Errors() map[string][]error { 158 | return G.Errs 159 | } 160 | 161 | // Prints a report for the FrisbyGlobal Object 162 | // 163 | // If there are any errors, they will all be printed as well 164 | func (G *global_data) PrintReport() *global_data { 165 | fmt.Printf("\nFor %d requests made\n", G.NumRequest) 166 | if len(G.Errs) == 0 { 167 | fmt.Printf(" All tests passed\n") 168 | } else { 169 | fmt.Printf(" FAILED [%d/%d]\n", G.NumErrored, G.NumAsserts) 170 | for key, val := range G.Errs { 171 | fmt.Printf(" [%s]\n", key) 172 | for _, e := range val { 173 | fmt.Println(" - ", e) 174 | } 175 | } 176 | } 177 | 178 | return G 179 | } 180 | --------------------------------------------------------------------------------