{"id":13564218,"url":"https://github.com/nao1215/spectest","last_synced_at":"2025-04-10T16:12:39.607Z","repository":{"id":198238833,"uuid":"700371024","full_name":"nao1215/spectest","owner":"nao1215","description":"A simple and extensible behavioural testing library for Go. You can use api test to simplify REST API, HTTP handler and e2e tests. ","archived":false,"fork":false,"pushed_at":"2024-05-20T12:27:59.000Z","size":3533,"stargazers_count":36,"open_issues_count":17,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-05-22T04:54:30.707Z","etag":null,"topics":["api","apitest","cross-platform","documentation","e2e-tests","forked-repo","go","golang","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nao1215.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"nao1215","patreon":"emigre680"}},"created_at":"2023-10-04T13:23:58.000Z","updated_at":"2024-06-13T08:10:54.626Z","dependencies_parsed_at":"2023-10-20T14:43:50.449Z","dependency_job_id":"59658f1c-d24c-4da0-b730-0a614073b9b4","html_url":"https://github.com/nao1215/spectest","commit_stats":null,"previous_names":["nao1215/spectest","go-spectest/spectest"],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Fspectest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Fspectest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Fspectest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nao1215%2Fspectest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nao1215","download_url":"https://codeload.github.com/nao1215/spectest/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248251201,"owners_count":21072686,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","apitest","cross-platform","documentation","e2e-tests","forked-repo","go","golang","testing-tools"],"created_at":"2024-08-01T13:01:28.190Z","updated_at":"2025-04-10T16:12:39.578Z","avatar_url":"https://github.com/nao1215.png","language":"Go","funding_links":["https://github.com/sponsors/nao1215","https://patreon.com/emigre680"],"categories":["Go"],"sub_categories":[],"readme":"[![Go Reference](https://pkg.go.dev/badge/github.com/nao1215/spectest.svg)](https://pkg.go.dev/github.com/nao1215/spectest)\n[![LinuxUnitTest](https://github.com/nao1215/spectest/actions/workflows/linux_test.yml/badge.svg)](https://github.com/nao1215/spectest/actions/workflows/linux_test.yml)\n[![MacUnitTest](https://github.com/nao1215/spectest/actions/workflows/mac_test.yml/badge.svg)](https://github.com/nao1215/spectest/actions/workflows/mac_test.yml)\n[![WindowsUnitTest](https://github.com/nao1215/spectest/actions/workflows/windows_test.yml/badge.svg)](https://github.com/nao1215/spectest/actions/workflows/windows_test.yml)\n[![UnitTestExampleCodes](https://github.com/nao1215/spectest/actions/workflows/test-examples.yml/badge.svg)](https://github.com/nao1215/spectest/actions/workflows/test-examples.yml)\n[![reviewdog](https://github.com/nao1215/spectest/actions/workflows/reviewdog.yml/badge.svg)](https://github.com/nao1215/spectest/actions/workflows/reviewdog.yml)\n![Coverage](https://github.com/nao1215/octocovs-central-repo/blob/main//badges/nao1215/spectest/coverage.svg?raw=true)\n\n## What is spectest?\n\nA simple and extensible behavioral testing library. Supports mocking external http calls and renders sequence diagrams on completion. In behavioral tests the internal structure of the app is not known by the tests. Data is input to the system and the outputs are expected to meet certain conditions.\n\n**This project is forked from [steinfletcher/apitest](https://github.com/steinfletcher/apitest)** apitest was functionally complete. However, I wanted more features, so I decided to fork it to actively develop it further. I will mainly enhance document generation and integration with AWS. There are no plans for compatibility between apitest and spectest. Therefore, future development will include BREAKING CHANGES. \n\nThe spectest has its own unique use cases, [Use Cases of spectest](./doc/spectest.md). Please refer to this document for more information.\n\n## Supported OS\n- Linux\n- Mac\n- Windows (Original [apitest](https://github.com/steinfletcher/apitest) does not support Windows)\n\n## Installation\n\n```bash\ngo get -u github.com/nao1215/spectest\n```\n\n## Demo\n\n![animated gif](./spectest.gif)\n\n## Examples\n\n### Framework and library integration examples\n\n| Example                                                                                              | Comment                                                                                                    |\n| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |\n| [gin](https://github.com/nao1215/spectest/tree/main/examples/gin)                             | popular martini-like web framework                                                                         |\n| [graphql](https://github.com/nao1215/spectest/tree/main/examples/graphql)                     | using gqlgen.com to generate a graphql server                                                              |\n| [gorilla](https://github.com/nao1215/spectest/tree/main/examples/gorilla)                     | the gorilla web toolkit                                                                                    |\n| [iris (broken)](https://github.com/nao1215/spectest/tree/main/examples/iris)                           | iris web framework                                                                                         |\n| [echo](https://github.com/nao1215/spectest/tree/main/examples/echo)                           | High performance, extensible, minimalist Go web framework                                                  |\n| [fiber](https://github.com/nao1215/spectest/tree/main/examples/fiber)                         | Express inspired web framework written in Go                                                               |\n| [httprouter](https://github.com/nao1215/spectest/tree/main/examples/httprouter)               | High performance HTTP request router that scales well                                                      |\n| [mocks](https://github.com/nao1215/spectest/tree/main/examples/mocks)                         | example mocking out external http calls                                                                    |\n| [sequence diagrams](https://github.com/nao1215/spectest/tree/main/examples/sequence-diagrams) | generate sequence diagrams from tests |\n| [Ginkgo](https://github.com/nao1215/spectest/tree/main/examples/ginkgo) | Ginkgo BDD test framework|\n| [plantuml](https://github.com/nao1215/spectest/tree/main/examples/plantuml) | wxample generating plantuml|\n\n### Companion libraries (Side projects)\nIn the original apitest repository, side projects were managed in separate repositories. However, in spectest, these side projects are managed within the same repository. However, the difflib, which operates independently from spectest, and the malfunctioning aws package, are managed in separate repositories.\n  \n| Library                                                                 | Comment                                        |\n| ----------------------------------------------------------------------- | -----------------------------------------------|\n| [JSON Path](https://github.com/nao1215/spectest/tree/main/jsonpath)           | JSON Path assertion addons                      |\n| [JOSN Schema](https://github.com/nao1215/spectest/tree/main/jsonschema)               | JSON Schema assertion addons |\n| [CSS Selectors](https://github.com/nao1215/spectest/tree/main/css-selector)  | CSS selector assertion addons                  |\n| [PlantUML](https://github.com/nao1215/spectest/tree/main/plantuml)           | Export sequence diagrams as plantUML           |\n| [DynamoDB (broken)](https://github.com/nao1215/tree/main/aws)           | Add DynamoDB interactions to sequence diagrams |\n\n### Credits\n\nThis library was influenced by the following software packages:\n\n* [YatSpec](https://github.com/bodar/yatspec) for creating sequence diagrams from tests\n* [MockMVC](https://spring.io) and [superagent](https://github.com/visionmedia/superagent) for the concept and behavioral testing approach\n* [Gock](https://github.com/h2non/gock) for the approach to mocking HTTP services in Go\n* [Baloo](https://github.com/h2non/baloo) for API design\n\n### Code snippets\n\n#### JSON body matcher\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tHandler(handler).\n\t\tGet(\"/user/1234\").\n\t\tExpect(t).\n\t\tBody(`{\"id\": \"1234\", \"name\": \"Tate\"}`).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### JSONPath\n\nFor asserting on parts of the response body JSONPath may be used. A separate module must be installed which provides these assertions - `go get -u github.com/nao1215/spectest/jsonpath`. This is packaged separately to keep this library dependency free.\n\nGiven the response is `{\"a\": 12345, \"b\": [{\"key\": \"c\", \"value\": \"result\"}]}`\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tAssert(jsonpath.Contains(`$.b[? @.key==\"c\"].value`, \"result\")).\n\t\tEnd()\n}\n```\n\nand `jsonpath.Equals` checks for value equality\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tAssert(jsonpath.Equal(`$.a`, float64(12345))).\n\t\tEnd()\n}\n```\n\n#### Custom assert functions\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tAssert(func(res *http.Response, req *http.Request) error {\n\t\t\tassert.Equal(t, http.StatusOK, res.StatusCode)\n\t\t\treturn nil\n\t\t}).\n\t\tEnd()\n}\n```\n\n#### Assert cookies\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tPatch(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tCookies(spectest.Cookie(\"ABC\").Value(\"12345\")).\n\t\tCookiePresent(\"Session-Token\").\n\t\tCookieNotPresent(\"XXX\").\n\t\tCookies(\n\t\t\tspectest.Cookie(\"ABC\").Value(\"12345\"),\n\t\t\tspectest.Cookie(\"DEF\").Value(\"67890\"),\n\t\t).\n\t\tEnd()\n}\n```\n\n#### Assert headers\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tHeaders(map[string]string{\"ABC\": \"12345\"}).\n\t\tEnd()\n}\n```\n\n#### Mocking external http calls\n\n```go\nvar getUser = spectest.NewMock().\n\tGet(\"/user/12345\").\n\tRespondWith().\n\tBody(`{\"name\": \"jon\", \"id\": \"1234\"}`).\n\tStatus(http.StatusOK).\n\tEnd()\n\nvar getPreferences = spectest.NewMock().\n\tGet(\"/preferences/12345\").\n\tRespondWith().\n\tBody(`{\"is_contactable\": true}`).\n\tStatus(http.StatusOK).\n\tEnd()\n\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tMocks(getUser, getPreferences).\n\t\tHandler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tBody(`{\"name\": \"jon\", \"id\": \"1234\"}`).\n\t\tEnd()\n}\n```\n\n#### Generating sequence diagrams from tests\n\n```go\n\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tReport(spectest.SequenceDiagram()).\n\t\tMocks(getUser, getPreferences).\n\t\tHandler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tBody(`{\"name\": \"jon\", \"id\": \"1234\"}`).\n\t\tEnd()\n}\n```\n\nIt is possible to override the default storage location by passing the formatter instance `Report(spectest.NewSequenceDiagramFormatter(\".sequence-diagrams\"))`. If you want to change the report file name , you use `CustomReportName(\"file name is here\")` . By default, the hash value becomes the report file name. \n\nYou can bring your own formatter too if you want to produce custom output. By default a sequence diagram is rendered on a html page.\n\nThe spectest checks the Content Type of the response. If it's an image-related MIME type, the image will be displayed in the report. In  the apitest, binary data was being displayed.\n\n![DiagramSample](doc/image/sample_diagram.png)\n\n\nOne feature that does not exist in the apitest fork is the ability to output reports in markdown format. The below code snippet is an example of how to output a markdown report.\n\n```go\n\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tCustomReportName(\"markdow_report\").\n\t\tReport(spectest.SequenceReport(spectest.ReportFormatterConfig{\n\t\t\tPath: \"doc\",\n\t\t\tKind: spectest.ReportKindMarkdown,\n\t\t})).\n\t\tHandler(handler).\n\t\tGet(\"/image\").\n\t\tExpect(t).\n\t\tBody(string(body)).\n\t\tHeader(\"Content-Type\", \"image/png\").\n\t\tHeader(\"Content-Length\", fmt.Sprint(imageInfo.Size())).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n![MarkdownReportSample](doc/image/markdown_report.png)\n\n#### Debugging http requests and responses generated by api test and any mocks\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tDebug().\n\t\tHandler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide basic auth in the request\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tBasicAuth(\"username\", \"password\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Pass a custom context to the request\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tWithContext(context.TODO()).\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide cookies in the request\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tCookies(spectest.Cookie(\"ABC\").Value(\"12345\")).\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide headers in the request\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tDelete(\"/hello\").\n\t\tHeaders(map[string]string{\"My-Header\": \"12345\"}).\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide query parameters in the request\n\n`Query`, `QueryParams` and `QueryCollection` can all be used in combination \n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tQueryParams(map[string]string{\"a\": \"1\", \"b\": \"2\"}).\n\t\tQuery(\"c\", \"d\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\nProviding `{\"a\": {\"b\", \"c\", \"d\"}` results in parameters encoded as `a=b\u0026a=c\u0026a=d`.\n`QueryCollection` can be used in combination with `Query`\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tGet(\"/hello\").\n\t\tQueryCollection(map[string][]string{\"a\": {\"b\", \"c\", \"d\"}}).\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide a url encoded form body in the request\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tPost(\"/hello\").\n\t\tFormData(\"a\", \"1\").\n\t\tFormData(\"b\", \"2\").\n\t\tFormData(\"b\", \"3\").\n\t\tFormData(\"c\", \"4\", \"5\", \"6\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Provide a multipart/form-data\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tPost(\"/hello\").\n\t\tMultipartFormData(\"a\", \"1\", \"2\").\n\t\tMultipartFile(\"file\", \"path/to/some.file1\", \"path/to/some.file2\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Capture the request and response data\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.New().\n\t\tObserve(func(res *http.Response, req *http.Request, specTest *spectest.SpecTest) {\n\t\t\t// do something with res and req\n\t\t}).\n\t\tHandler(handler).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n#### Intercept the request\n\nThis is useful for mutating the request before it is sent to the system under test.\n\n```go\nfunc TestApi(t *testing.T) {\n\tspectest.Handler(handler).\n\t\tIntercept(func(req *http.Request) {\n\t\t\treq.URL.RawQuery = \"a[]=xxx\u0026a[]=yyy\"\n\t\t}).\n\t\tGet(\"/hello\").\n\t\tExpect(t).\n\t\tStatus(http.StatusOK).\n\t\tEnd()\n}\n```\n\n## Contributing\n\nView the [contributing guide](CONTRIBUTING.md).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnao1215%2Fspectest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnao1215%2Fspectest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnao1215%2Fspectest/lists"}