{"id":20670469,"url":"https://github.com/caicloud/aloe","last_synced_at":"2025-04-19T18:23:42.003Z","repository":{"id":57493035,"uuid":"115418116","full_name":"caicloud/aloe","owner":"caicloud","description":"Declarative API test framework based on ginkgo and gomega","archived":false,"fork":false,"pushed_at":"2019-09-10T07:54:51.000Z","size":2797,"stargazers_count":20,"open_issues_count":4,"forks_count":14,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-03-29T11:34:40.719Z","etag":null,"topics":["api","engineering","test"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/caicloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-26T11:56:00.000Z","updated_at":"2025-02-12T09:50:32.000Z","dependencies_parsed_at":"2022-08-28T13:40:14.559Z","dependency_job_id":null,"html_url":"https://github.com/caicloud/aloe","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caicloud%2Faloe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caicloud%2Faloe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caicloud%2Faloe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caicloud%2Faloe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/caicloud","download_url":"https://codeload.github.com/caicloud/aloe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249762236,"owners_count":21321897,"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","engineering","test"],"created_at":"2024-11-16T20:20:43.816Z","updated_at":"2025-04-19T18:23:41.986Z","avatar_url":"https://github.com/caicloud.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Aloe\n\n[![Build\nStatus](https://travis-ci.org/caicloud/aloe.svg?branch=master)](https://travis-ci.org/caicloud/aloe)\n[![Coverage\nStatus](https://coveralls.io/repos/github/caicloud/aloe/badge.svg?branch=master)](https://coveralls.io/github/caicloud/aloe?branch=master)\n[![GoDoc](http://godoc.org/github.com/caicloud/aloe?status.svg)](http://godoc.org/github.com/caicloud/aloe)\n[![Go Report\nCard](https://goreportcard.com/badge/github.com/caicloud/aloe)](https://goreportcard.com/report/github.com/caicloud/aloe)\n\nAloe is a declarative API test framework based on\n[ginkgo](https://github.com/onsi/ginkgo),\n[gomega](https://github.com/onsi/gomega)\nand [yaml](http://yaml.org/). It aims to help write and read API test cases\nsimpler.\n\nDISCLAIMER:\n- only json API is supported now\n- avoid using Aloe for extremely complex test, use ginkgo directly instead\n\n## Terminology\n\nThere are two important concepts in aloe:\n\n- `case`: case means a test case, for example, get an endpoint and verify its\n  output.\n- `context`: to simply put, a context is a group of cases. Usually, context is\n  used to init data in database, so that all test cases will be tested in a\n  determined context.\n\n## Getting Started\n\nFollowing is a getting started example for using Aloe. First, create a\ndirectory to put `context` and `case` yaml files.\n\n```\nmkdir -p test/testdata\n```\n\nThen, define your `context` in `context.yaml`.\n\n```yaml\n# test/testdata/context.yaml\nsummary: \"CRUD Test example\"\nflow:\n- description: \"Init a product\"\n  request:\n    api: POST /products\n    headers:\n      \"Content-Type\": \"application/json\"\n    body: |\n      {\n        \"id\": \"1\",\n        \"title\": \"test\"\n      }\n  response:\n    statusCode: 201\n  definitions:\n  - name: \"testProductId\"\n    selector:\n    - \"id\"\n```\n\nAs mentioned above, a context is used to run a group of test cases in a\ndetermined environment. In the above example,\nwe define a context which simply sends a POST request to `/products` with\nproduct name `test`; therefore, all test\ncases in this context will expect product `test` exists.\n\nNow with context setup, we can start defining `case`. Here, we define a test\ncase in `test/testdata/get.yaml` to get\nand verify product `test`.\n\n```yaml\n# test/testdata/get.yaml\ndescription: \"Try to GET a product\"\nflow:\n- description: \"Get the product with title test\"\n  request:\n    api: GET /products/%{testProductId}\n    headers:\n      \"Content-Type\": \"application/json\"\n  response:\n    statusCode: 200\n```\n\nFinally, some go codes should be written in `test` directory to run the test\ncase:\n\n```go\nfunc init() {\n\taloe.Init(nil)\n}\n\nfunc RunTEST(t *testing.T) {\n\taloe.AppendDataDirs(\"testdata\")\n\tif err := aloe.Env(\"host\", \"localhost:8080\"); err != nil {\n\t\tfmt.Printf(\"can't set env host: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tif err := aloe.RegisterCleaner(s); err != nil {\n\t\tfmt.Printf(\"can't register cleaner: %v\", err)\n\t\tos.Exit(1)\n\t}\n\taloe.Run(t)\n}\n\nvar _ = ginkgo.BeforeSuite(func() {\n    s.Register()\n    listener, err := net.Listen(\"tcp\", \":8080\")\n    if err != nil {\n        fmt.Println(\"can't begin server\")\n        os.Exit(1)\n    }\n    go http.Serve(listener, nil)\n})\n```\n\n## Usage\n\n### Variable\n\nVariables can be defined to hold auto-generated value by server (e.g. id). For\nexample:\n\n```yaml\nflow:\n- description: \"Init a product\"\n  definitions:\n  - name: \"testProductId\"\n    selector:\n    - \"id\"\n```\n\nA variable called `testProductId` will be defined. The value of `testProductId`\nis from the round trip response.\n\nVariable can also be a json snippet.\n\n```\nresponse:\n{\n    \"id\": \"111\",\n    \"title\": \"test\",\n    \"comments\":[\n        \"aaa\",\n        \"bbb\"\n    ]\n}\n\n// id will select value of string\n[\"id\"] =\u003e 111(without quote)\n\n// empty selector will select whole body\n[] =\u003e {\n    \"id\": \"111\",\n    \"title\": \"test\",\n    \"comments\":[\n        \"aaa\",\n        \"bbb\"\n    ]\n}\n\n// comments will select partial body\n[\"comments\"] =\u003e [\n    \"aaa\",\n    \"bbb\"\n]\n\n// comments.0 will select first element of array\n[\"comments\", \"0\"] =\u003e aaa\n\n```\n\nIf a variables is defined, it can be used in round trip with format `%{name}`.\n\n### Body validator\n\nBody validator is used to validate response fields. Some special validators are\npredefined, e.g. `$regexp`\n\n```yaml\nflow:\n- description: \"Create a product\"\n  response:\n    # validate that id format matches regexp\n    # validate that password field is not returned\n    body: |\n      {\n        \"id\": {\n          \"$regexp\": \"[a-zA-Z][a-zA-Z0-9-]{11}\"\n        },\n        \"password\": {\n          \"$exists\": false,\n        },\n      }\n```\n\nNow only `$regexp` and `$exists` is supported (more special validator will be\nadded in the future).\n\n### Cleaner\n\nCleaner can be used to clean context after all cases in the context are\nfinished.\n\n```go\ntype Cleaner interface {\n    // Name defines cleaner name\n    Name() string\n\n    // Clean will be called after all of the cases in the context are\n    // finished\n    Clean(variables map[string]jsonutil.Variable) error\n}\n\n```\n\nUsers can implement their own cleaners and call RegisterCleaner in framework.\nThen cleaners can be used in context file.\n\n```yaml\n# test/testdata/get.yaml\nsummary: \"Create a product\"\nflow:\n- description: \"Create a product\"\n  request:\n    api: POST /products\n  response:\n    statusCode: 201\ncleaner: \"productCleaner\"\n```\n\n### Presetter\n\nPresetter can be used to preset all RoundTrips in the context.\n\n```go\n// Presetter defines presetter\ntype Presetter interface {\n    // Name defines name of presetter\n    Name() string\n\n    // Preset parse args and set roundtrip template\n    Preset(rt *types.RoundTrip, args map[string]string) (*types.RoundTrip,\n    error)\n}\n```\n\nUsers can implement their own presetters and call RegisterPresetter in\nframework.\nThen presetters can be used in context file.\n\n```yaml\n# test/testdata/get.yaml\nsummary: \"Create a product\"\npresetter:\n- name: \"header\"\n  args:\n    content-type: application/json\nflow:\n- description: \"Create a product\"\n  request:\n    api: POST /products\n  response:\n    statusCode: 201\n```\n\n### Nested context\n\nContext can be nested just like directory. Child context will see all setup in\nparent context.\n\n```\ntests\n└── testdata\n    ├── _context.yaml\n    ├── basic\n    │   ├── _context.yaml\n    │   ├── create.yaml\n    │   └── update.yaml\n    ├── failure\n    │   ├── _context.yaml\n    │   └── create.yaml\n    └── list\n        ├── _context.yaml\n        └── list_all.yaml\n```\n\n## Examples\n\nFor more examples, see:\n\n- [crud](./examples/crud)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaicloud%2Faloe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaicloud%2Faloe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaicloud%2Faloe/lists"}