{"id":46829280,"url":"https://github.com/gdt-dev/gdt","last_synced_at":"2026-03-10T09:47:10.242Z","repository":{"id":183801607,"uuid":"670712343","full_name":"gdt-dev/gdt","owner":"gdt-dev","description":"Go Declarative Testing","archived":false,"fork":false,"pushed_at":"2026-01-02T17:47:34.000Z","size":359,"stargazers_count":10,"open_issues_count":4,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-01-09T04:27:58.914Z","etag":null,"topics":["declarative-testing","go","go-testing","golang","golang-testing"],"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/gdt-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"COPYING","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-07-25T16:56:09.000Z","updated_at":"2026-01-02T17:47:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"42c6c3a2-b835-4b18-b5ae-e3c1519d0f05","html_url":"https://github.com/gdt-dev/gdt","commit_stats":null,"previous_names":["gdt-dev/gdt"],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/gdt-dev/gdt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gdt-dev%2Fgdt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gdt-dev%2Fgdt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gdt-dev%2Fgdt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gdt-dev%2Fgdt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gdt-dev","download_url":"https://codeload.github.com/gdt-dev/gdt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gdt-dev%2Fgdt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30329159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T05:25:20.737Z","status":"ssl_error","status_checked_at":"2026-03-10T05:25:17.430Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["declarative-testing","go","go-testing","golang","golang-testing"],"created_at":"2026-03-10T09:47:09.221Z","updated_at":"2026-03-10T09:47:10.201Z","avatar_url":"https://github.com/gdt-dev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `gdt` - The Go Declarative Testing framework\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/gdt-dev/gdt.svg)](https://pkg.go.dev/github.com/gdt-dev/gdt)\n[![Go Report Card](https://goreportcard.com/badge/github.com/gdt-dev/gdt)](https://goreportcard.com/report/github.com/gdt-dev/gdt)\n[![Build Status](https://github.com/gdt-dev/gdt/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/gdt-dev/gdt/actions)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)\n\n\u003cdiv style=\"float: left\"\u003e\n\u003cimg align=left src=\"static/gdtlogo400x544.png\" width=200px /\u003e\n\u003c/div\u003e\n\n`gdt` is a testing library that allows test authors to cleanly describe tests\nin a YAML file. `gdt` reads YAML files that describe a test's assertions and\nthen builds a set of Go structures that the standard Go\n[`testing`](https://golang.org/pkg/testing/) package and standard `go test`\ntool can execute.\n\n## Introduction\n\nWriting functional tests in Go can be overly verbose and tedious. When the code\nthat tests some part of an application is verbose or tedious, then it becomes\ndifficult to read the tests and quickly understand the assertions the test is\nmaking.\n\nThe more difficult it is to understand the test assertions or the test setups\nand assumptions, the greater the chance that the test improperly validates the\napplication behaviour. Furthermore, test code that is cumbersome to read is\nprone to bit-rot due to its high maintenance cost. This is particularly true\nfor code that verifies an application's integration points with *other*\napplications via an API.\n\nThe idea behind `gdt` is to allow test authors to **cleanly** and **clearly**\ndescribe a functional test's **assumptions** and **assertions** in a\ndeclarative format.\n\nSeparating the *description* of a test's assumptions (setup) and assertions\nfrom the Go code that actually performs the test assertions leads to tests\nthat are easier to read and understand. This allows developers to spend *more\ntime writing code* and less time copy/pasting boilerplate test code. Due to the\neasier test comprehension, `gdt` also encourages writing greater quality and\ncoverage of functional tests.\n\nInstead of developers writing code that looks like this:\n\n```go\nvar _ = Describe(\"Books API - GET /books failures\", func() {\n    var response *http.Response\n    var err error\n    var testPath = \"/books/nosuchbook\"\n\n    BeforeEach(func() {\n        response, err = http.Get(apiPath(testPath))\n        Ω(err).Should(BeZero())\n    })\n\n    Describe(\"failure modes\", func() {\n        Context(\"when no such book was found\", func() {\n            It(\"should not include JSON in the response\", func() {\n                Ω(respJSON(response)).Should(BeZero())\n            })\n            It(\"should return 404\", func() {\n                Ω(response.StatusCode).Should(Equal(404))\n            })\n        })\n    })\n})\n```\n\nthey can instead have a test that looks like this:\n\n\n```yaml\nfixtures:\n - books_api\ntests:\n - name: no such book was found\n   GET: /books/nosuchbook\n   response:\n     json:\n       len: 0\n     status: 404\n```\n\n## Coming from Ginkgo\n\nWhen using Ginkgo, developers create tests for a particular module (say, the\n`books` module) by creating a `books_test.go` file and calling some Ginkgo\nfunctions in a BDD test style. A sample Ginkgo test might look something like\nthis:\n\n```go\npackage api_test\n\nimport (\n    \"github.com/gdt-dev/examples/books/api\"\n    . \"github.com/onsi/ginkgo\"\n    . \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Books API Types\", func() {\n    var (\n        longBook  api.Book\n        shortBook api.Book\n    )\n\n    BeforeEach(func() {\n        longBook = api.Book{\n            Title: \"Les Miserables\",\n            Pages: 1488,\n            Author: \u0026api.Author{\n                Name: \"Victor Hugo\",\n            },\n        }\n\n        shortBook = api.Book{\n            Title: \"Fox In Socks\",\n            Pages: 24,\n            Author: \u0026api.Author{\n                Name: \"Dr. Seuss\",\n            },\n        }\n    })\n\n    Describe(\"Categorizing book length\", func() {\n        Context(\"With more than 300 pages\", func() {\n            It(\"should be a novel\", func() {\n                Expect(longBook.CategoryByLength()).To(Equal(\"NOVEL\"))\n            })\n        })\n\n        Context(\"With fewer than 300 pages\", func() {\n            It(\"should be a short story\", func() {\n                Expect(shortBook.CategoryByLength()).To(Equal(\"SHORT STORY\"))\n            })\n        })\n    })\n})\n```\n\n\nThis is perfectly fine for simple unit tests of Go code. However, once the\ntests begin to call multiple APIs or packages, the Ginkgo Go tests start to get\ncumbersome. Consider the following example of *functionally* testing the\nfailure modes for a simple HTTP REST API endpoint:\n\n```go\npackage api_test\n\nimport (\n    \"io/ioutil\"\n    \"log\"\n    \"net/http\"\n    \"net/http/httptest\"\n    \"os\"\n    \"strings\"\n\n    . \"github.com/onsi/ginkgo\"\n    . \"github.com/onsi/gomega\"\n\n    \"github.com/gdt-dev/examples/http/api\"\n)\n\nvar (\n    server *httptest.Server\n)\n\n// respJSON returns a string if the supplied HTTP response body is JSON,\n// otherwise the empty string\nfunc respJSON(r *http.Response) string {\n    if r == nil {\n        return \"\"\n    }\n    if !strings.HasPrefix(r.Header.Get(\"content-type\"), \"application/json\") {\n        return \"\"\n    }\n    bodyStr, _ := ioutil.ReadAll(r.Body)\n    return string(bodyStr)\n}\n\n// respText returns a string if the supplied HTTP response has a text/plain\n// content type and a body, otherwise the empty string\nfunc respText(r *http.Response) string {\n    if r == nil {\n        return \"\"\n    }\n    if !strings.HasPrefix(r.Header.Get(\"content-type\"), \"text/plain\") {\n        return \"\"\n    }\n    bodyStr, _ := ioutil.ReadAll(r.Body)\n    return string(bodyStr)\n}\n\nfunc apiPath(path string) string {\n    return strings.TrimSuffix(server.URL, \"/\") + \"/\" + strings.TrimPrefix(path, \"/\")\n}\n\n// Register an HTTP server fixture that spins up the API service on a\n// random port on localhost\nvar _ = BeforeSuite(func() {\n    logger := log.New(os.Stdout, \"http: \", log.LstdFlags)\n    c := api.NewControllerWithBooks(logger, nil)\n    server = httptest.NewServer(c.Router())\n})\n\nvar _ = AfterSuite(func() {\n    server.Close()\n})\n\nvar _ = Describe(\"Books API - GET /books failures\", func() {\n    var response *http.Response\n    var err error\n    var testPath string\n\n    BeforeEach(func() {\n        response, err = http.Get(apiPath(testPath))\n        Ω(err).Should(BeZero())\n    })\n\n    Describe(\"failure modes\", func() {\n        AssertZeroJSONLength := func() {\n            It(\"should not include JSON in the response\", func() {\n                Ω(respJSON(response)).Should(BeZero())\n            })\n        }\n\n        Context(\"when no such book was found\", func() {\n            JustBeforeEach(func() {\n                testPath = \"/books/nosuchbook\"\n            })\n\n            AssertZeroJSONLength()\n\n            It(\"should return 404\", func() {\n                Ω(response.StatusCode).Should(Equal(404))\n            })\n        })\n\n        Context(\"when an invalid query parameter is supplied\", func() {\n            JustBeforeEach(func() {\n                testPath = \"/books?invalidparam=1\"\n            })\n\n            AssertZeroJSONLength()\n\n            It(\"should return 400\", func() {\n                Ω(response.StatusCode).Should(Equal(400))\n            })\n            It(\"should indicate invalid query parameter\", func() {\n                Ω(respText(response)).Should(ContainSubstring(\"invalid parameter\"))\n            })\n        })\n    })\n})\n```\n\nThe above test code obscures what is being tested by cluttering the test\nassertions with the Go closures and accessor code. Compare the above with\nhow `gdt` allows the test author to describe the same assertions:\n\n```yaml\nfixtures:\n - books_api\ntests:\n - name: no such book was found\n   GET: /books/nosuchbook\n   response:\n     json:\n       len: 0\n     status: 404\n - name: invalid query parameter is supplied\n   GET: /books?invalidparam=1\n   response:\n     json:\n       len: 0\n     status: 400\n     strings:\n       - invalid parameter\n```\n\nNo more closures and boilerplate function code getting in the way of expressing\nthe assertions, which should be the focus of the test.\n\nThe more intricate the assertions being verified by the test, generally the\nmore verbose and cumbersome the Go test code tends to become. First and\nforemost, tests should be *readable*. If they are not readable, then the test's\nassertions are not *understandable*. And tests that cannot easily be understood\nare often the source of bit rot and technical debt. Worse, tests that aren't\nunderstandable stand a greater chance of having an improper assertion go\nundiscovered, leading to tests that validate the wrong behaviour or don't\nvalidate the correct behaviour.\n\nConsider a Ginkgo test case that checks the following behaviour:\n\n* When a book is created via a call to `POST /books`, we are able to get book\n information from the link returned in the HTTP response's `Location` header\n* The newly-created book's author name should be set to a known value\n* The newly-created book's ID field is a valid UUID\n* The newly-created book's publisher has an address containing a known state code\n\nA typical implementation of a Ginkgo test might look like this:\n\n```go\npackage api_test\n\nimport (\n    \"bytes\"\n    \"encoding/json\"\n    \"net/http\"\n\n    . \"github.com/onsi/ginkgo\"\n    . \"github.com/onsi/gomega\"\n\n    \"github.com/gdt-dev/examples/http/api\"\n)\n\nvar _ = Describe(\"Books API - POST /books -\u003e GET /books from Location\", func() {\n\n    var err error\n    var resp *http.Response\n    var locURL string\n    var authorID, publisherID string\n\n    Describe(\"proper HTTP GET after POST\", func() {\n\n        Context(\"when creating a single book resource\", func() {\n            It(\"should be retrievable via GET {location header}\", func() {\n                // See https://github.com/onsi/ginkgo/issues/457 for why this\n                // needs to be here instead of in the outer Describe block.\n                authorID = getAuthorByName(\"Ernest Hemingway\").ID\n                publisherID = getPublisherByName(\"Charles Scribner's Sons\").ID\n                req := api.CreateBookRequest{\n                    Title:       \"For Whom The Bell Tolls\",\n                    AuthorID:    authorID,\n                    PublisherID: publisherID,\n                    PublishedOn: \"1940-10-21\",\n                    Pages:       480,\n                }\n                var payload []byte\n                payload, err = json.Marshal(\u0026req)\n                if err != nil {\n                    Fail(\"Failed to serialize JSON in setup\")\n                }\n                resp, err = http.Post(apiPath(\"/books\"), \"application/json\", bytes.NewBuffer(payload))\n                Ω(err).Should(BeNil())\n\n                // See https://github.com/onsi/ginkgo/issues/70 for why this\n                // has to be one giant It() block. The GET tests rely on the\n                // result of an earlier POST response (for the Location header)\n                // and therefore all of the assertions below must be in a\n                // single It() block. :(\n\n                Ω(resp.StatusCode).Should(Equal(201))\n                Ω(resp.Header).Should(HaveKey(\"Location\"))\n\n                locURL = resp.Header[\"Location\"][0]\n\n                resp, err = http.Get(apiPath(locURL))\n                Ω(err).Should(BeNil())\n\n                Ω(resp.StatusCode).Should(Equal(200))\n\n                var book api.Book\n\n                err := json.Unmarshal([]byte(respJSON(resp)), \u0026book)\n                Ω(err).Should(BeNil())\n\n                Ω(IsValidUUID4(book.ID)).Should(BeTrue())\n                Ω(book.Author).ShouldNot(BeNil())\n                Ω(book.Author.Name).Should(Equal(\"Ernest Hemingway\"))\n                Ω(book.Publisher).ShouldNot(BeNil())\n                Ω(book.Publisher.Address).ShouldNot(BeNil())\n                Ω(book.Publisher.Address.State).Should(Equal(\"NY\"))\n            })\n        })\n    })\n})\n```\n\nCompare the above test code to the following YAML document that a `gdt` user\nmight create to describe the same assertions:\n\n```yaml\nfixtures:\n - books_api\n - books_data\ntests:\n - name: create a new book\n   POST: /books\n   data:\n     title: For Whom The Bell Tolls\n     published_on: 1940-10-21\n     pages: 480\n     author_id: $.authors.by_name[\"Ernest Hemingway\"].id\n     publisher_id: $.publishers.by_name[\"Charles Scribner's Sons\"].id\n   response:\n     status: 201\n     headers:\n      - Location\n - name: look up that created book\n   GET: $$LOCATION\n   response:\n     status: 200\n     json:\n       paths:\n         $.author.name: Ernest Hemingway\n         $.publisher.address.state: New York\n       path-formats:\n         $.id: uuid4\n```\n\n## `gdt` test scenario structure\n\nA `gdt` test scenario (or just \"scenario\") is simply a YAML file.\n\nAll `gdt` scenarios have the following fields:\n\n* `name`: (optional) string describing the contents of the test file. If\n  missing or empty, the filename is used as the name\n* `description`: (optional) string with longer description of the test file\n  contents\n* `defaults`: (optional) is a map of default options and configuration values\n* `fixtures`: (optional) list of strings indicating named fixtures that will be\n  started before any of the tests in the file are run\n* `skip-if`: (optional) list of [`Spec`][basespec] specializations that will be\n  evaluated *before* running any test in the scenario. If any of these\n  conditions evaluates successfully, the test scenario will be skipped.\n* `tests`: list of [`Spec`][basespec] specializations that represent the\n  runnable test units in the test scenario.\n\n[basespec]: https://github.com/gdt-dev/core/blob/023f92ee3468852d1d477df91cf42789e472b3b5/api/spec.go#L27-L48\n\nThe scenario's `tests` field is the most important and the [`Spec`][basespec]\nobjects that it contains are the meat of a test scenario.\n\n### `gdt` test spec structure\n\nA spec represents a single *action* that is taken and zero or more\n*assertions* that represent what you expect to see resulting from that action.\n\n`gdt` plugins each define a specialized subclass of the base [`Spec`][basespec]\nthat contains fields that are specific to that type of test.\n\nFor example, there is an [`exec`][exec-plugin] plugin that allows you to\nexecute arbitrary commands and assert expected result codes and output. There\nis an [`http`][http-plugin] that allows you to call an HTTP URL and assert that\nthe response looks like what you expect. There is a [`kube`][kube-plugin]\nplugin that allows you to interact with a Kubernetes API, etc.\n\n`gdt` examines the YAML file that defines your test scenario and uses these\nplugins to parse individual test specs.\n\nAll test specs have the following fields:\n\n* `name`: (optional) string describing the test unit.\n* `description`: (optional) string with longer description of the test unit.\n* `timeout`: (optional) a string duration of time the test unit is expected to\n  complete within.\n* `retry`: (optional) an object containing retry configurationu for the test\n  unit. Some plugins will automatically attempt to retry the test action when\n  an assertion fails. This field allows you to control this retry behaviour for\n  each individual test.\n* `retry.interval`: (optional) a string duration of time that the test plugin\n  will retry the test action in the event assertions fail. The default interval\n  for retries is plugin-dependent.\n* `retry.attempts`: (optional) an integer indicating the number of times that a\n  plugin will retry the test action in the event assertions fail. The default\n  number of attempts for retries is plugin-dependent.\n* `retry.exponential`: (optional) a boolean indicating an exponential backoff\n  should be applied to the retry interval. The default is is plugin-dependent.\n* `wait` (optional) an object containing [wait information][wait] for the test\n  unit.\n* `wait.before`: a string duration of time that gdt should wait before\n  executing the test unit's action.\n* `wait.after`: a string duration of time that gdt should wait after executing\n  the test unit's action.\n* `on`: (optional) an object describing actions to take upon certain\n  conditions.\n* `on.fail`: (optional) an object describing an action to take when any\n  assertion fails for the test action.\n* `on.fail.exec`: a string with the exact command to execute upon test\n  assertion failure. You may execute more than one command but must include the\n  `on.fail.shell` field to indicate that the command should be run in a shell.\n* `on.fail.shell`: (optional) a string with the specific shell to use in executing the\n  command to run upon test assertion failure. If empty (the default), no shell\n  is used to execute the command and instead the operating system's `exec` family\n  of calls is used.\n\n[exec-plugin]: https://github.com/gdt-dev/core/tree/023f92ee3468852d1d477df91cf42789e472b3b5/plugin/exec\n[http-plugin]: https://github.com/gdt-dev/http\n[kube-plugin]: https://github.com/gdt-dev/kube\n[wait]: https://github.com/gdt-dev/core/blob/023f92ee3468852d1d477df91cf42789e472b3b5/api/wait.go#L11-L25\n\n#### `exec` test spec structure\n\nThe `exec` plugin's test spec allows test authors to execute arbitrary commands and\nassert that the command results in an expected result code or output.\n\nIn addition to all the base `Spec` fields listed above, the `exec` plugin's\ntest spec also contains these fields:\n\n* `exec`: a string with the exact command to execute. You may execute more than\n  one command but must include the `shell` field to indicate that the command\n  should be run in a shell. It is best practice, however, to simply use\n  multiple `exec` specs instead of executing multiple commands in a single\n  shell call.\n* `shell`: (optional) a string with the specific shell to use in executing the\n  command. If empty (the default), no shell is used to execute the command and\n  instead the operating system's `exec` family of calls is used.\n* `var-stdout`: (optional) a string with the name of a variable to save the\n  contents of the test spec's `stdout` stream. This named variable can then be\n  referred from subsequent test specs. Note: this is a shortcut for the\n  longer-form `var:{VAR_NAME}:from:stdout`\n* `var-stderr`: (optional) a string with the name of a variable to save the\n  contents of the test spec's `stderr` stream. This named variable can then be\n  referred from subsequent test specs. Note: this is a shortcut for the\n  longer-form `var:{VAR_NAME}:from:stderr`\n* `var-rc`: (optional) a string with the name of a variable to save the\n  contents of the test spec's return/exitcode value. This named variable can\n  then be referred from subsequent test specs. Note: this is a shortcut for the\n  longer-form `var:{VAR_NAME}:from:returncode`\n* `var`: (optional) an object describing variables that can have values saved\n  and referred to by subsequent test specs. Each key in the `var` object is the\n  name of the variable to define. The `var.from` field contains a string\n  describing where the value for the variable should be sourced.\n* `var.$VARIABLE_NAME.from`: (required) a string describing where the variable\n  with name `$VARIABLE_NAME` should source its value. The strings `stdout`,\n  `stderr` and `returncode` refer to the corresponding stdout, stderr\n  and return/exitcode values. All other string values for `var.from` indicate\n  the name of the environment variable to read into the named variable.\n* `assert`: (optional) an object describing the conditions that will be\n  asserted about the test action.\n* `assert.exit-code`: (optional) an integer with the expected exit code from the\n  executed command. The default successful exit code is 0 and therefore you do\n  not need to specify this if you expect a successful exit code.\n* `assert.out`: (optional) a [`PipeExpect`][pipeexpect] object containing\n  assertions about content in `stdout`.\n* `assert.out.is`: (optional) a string with the exact contents of `stdout` you expect\n  to get.\n* `assert.out.all`: (optional) a string or list of strings that *all* must be\n  present in `stdout`.\n* `assert.out.any`: (optional) a string or list of strings of which *at\n  least one* must be present in `stdout`.\n* `assert.out.none`: (optional) a string or list of strings of which *none\n  should be present* in `stdout`.\n* `assert.err`: (optional) a [`PipeAssertions`][pipeexpect] object containing\n  assertions about content in `stderr`.\n* `assert.err.is`: (optional) a string with the exact contents of `stderr` you expect\n  to get.\n* `assert.err.all`: (optional) a string or list of strings that *all* must be\n  present in `stderr`.\n* `assert.err.any`: (optional) a string or list of strings of which *at\n  least one* must be present in `stderr`.\n* `assert.err.none`: (optional) a string or list of strings of which *none\n  should be present* in `stderr`.\n\n[execspec]: https://github.com/gdt-dev/core/blob/023f92ee3468852d1d477df91cf42789e472b3b5/plugin/exec/spec.go#L11-L24\n[pipeexpect]: https://github.com/gdt-dev/core/blob/023f92ee3468852d1d477df91cf42789e472b3b5/plugin/exec/assertions.go#L30-L41\n\n### Passing variables to subsequent test specs\n\nA `gdt` test scenario is comprised of a list of test specs. These test specs\nare executed in sequential order. If you want to have one test spec be able to\nuse some output or value calculated or asserted in a previous step, you can use\nthe `gdt` variable system.\n\nHere's an test scenario that shows how to define variables in a test spec and\nhow to use those variables in later test specs.\n\nfile: `plugin/exec/testdata/var-save-restore.yaml`:\n\n```yaml\nname: var-save-restore\ndescription: a scenario that tests variable save/restore across multiple test specs\ntests:\n  - exec: echo 42\n    var-stdout: VAR_STDOUT\n\n  - exec: echo $$VAR_STDOUT\n    var-rc: VAR_RC\n    assert:\n      out:\n        is: 42\n\n  - exec: echo $$VAR_RC\n    assert:\n      out:\n        is: 0\n\n  - exec: echo 42\n    assert:\n      out:\n        is: $$VAR_STDOUT\n```\n\nIn the first test spec, we specify that we want to store the value of the\n`stdout` stream in a variable called `VAR_STDOUT`:\n\n```yaml\n  - exec: echo 42\n    var-stdout: VAR_STDOUT\n```\n\nIn the second test spec, we refer to the `VAR_STDOUT` variable using the\ndouble-dollar-sign notation in the `exec` field and also specify a `VAR_RC`\nvariable to contain the value of the return/exitcode from the executed\nstatement (`echo 42`):\n\n```yaml\n  - exec: echo $$VAR_STDOUT\n    var-rc: VAR_RC\n    assert:\n      out:\n        is: 42\n```\n\n\u003e **NOTE**: We use the double-dollar-sign notation because by default, `gdt`\n\u003e replaces all single-dollar-sign notations with environment variables *BEFORE*\n\u003e executing the test specs in a test scenario. Using the double-dollar-sign\n\u003e notation means that environment variable substitution does not impact the\n\u003e referencing of `gdt` variables referenced in a test spec.\n\nIn the third test spec, we simply echo out the value of that `VAR_RC` variable\nand assert that the stdout stream contains the string \"0\" (since `echo 42`\nreturns 0.):\n\n```yaml\n  - exec: echo $$VAR_RC\n    assert:\n      out:\n        is: 0\n```\n\nFinally, in the fourth step, we demonstrate that we can refer to the\n`VAR_STDOUT` variable defined in the very first test spec from the\n`assert.out.is` field. This shows the flexibility of the `gdt` variable system.\nYou can define variables using a simple declarative syntax and then refer to\nthe value of those variables using the double-dollar-sign notation in any\nsubsequent test spec.\n\n### Timeouts and retrying assertions\n\nWhen evaluating assertions for a test spec, `gdt` inspects the test's\n`timeout` value to determine how long to retry the `get` call and recheck\nthe assertions.\n\nIf a test's `timeout` is empty, `gdt` inspects the scenario's\n`defaults.timeout` value. If both of those values are empty, `gdt` will look\nfor any default `timeout` value that the plugin uses.\n\nIf you're interested in seeing the individual results of `gdt`'s\nassertion-checks for a single `get` call, you can use the `gdt.WithDebug()`\nfunction, like this test function demonstrates:\n\nfile: `testdata/matches.yaml`:\n\n```yaml\nname: matches\ndescription: create a deployment and check the matches condition succeeds\nfixtures:\n  - kind\ntests:\n  - name: create-deployment\n    kube:\n      create: testdata/manifests/nginx-deployment.yaml\n  - name: deployment-exists\n    kube:\n      get: deployments/nginx\n    assert:\n      matches:\n        spec:\n          replicas: 2\n          template:\n            metadata:\n              labels:\n                app: nginx\n        status:\n          readyReplicas: 2\n  - name: delete-deployment\n    kube:\n      delete: deployments/nginx\n```\n\nfile: `matches_test.go`\n\n```go\nimport (\n    \"github.com/gdt-dev/gdt\"\n    _ \"github.com/gdt-dev/kube\"\n    kindfix \"github.com/gdt-dev/kube/fixture/kind\"\n)\n\nfunc TestMatches(t *testing.T) {\n\tfp := filepath.Join(\"testdata\", \"matches.yaml\")\n\n\tkfix := kindfix.New()\n\n\ts, err := gdt.From(fp)\n\n\tctx := gdt.NewContext(gdt.WithDebug())\n\tctx = gdt.RegisterFixture(ctx, \"kind\", kfix)\n\ts.Run(ctx, t)\n}\n```\n\nHere's what running `go test -v matches_test.go` would look like:\n\n```\n$ go test -v matches_test.go\n=== RUN   TestMatches\n=== RUN   TestMatches/matches\n=== RUN   TestMatches/matches/create-deployment\n=== RUN   TestMatches/matches/deployment-exists\ndeployment-exists (try 1 after 1.303µs) ok: false, terminal: false\ndeployment-exists (try 1 after 1.303µs) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject\ndeployment-exists (try 2 after 595.62786ms) ok: false, terminal: false\ndeployment-exists (try 2 after 595.62786ms) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject\ndeployment-exists (try 3 after 1.020003807s) ok: false, terminal: false\ndeployment-exists (try 3 after 1.020003807s) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject\ndeployment-exists (try 4 after 1.760006109s) ok: false, terminal: false\ndeployment-exists (try 4 after 1.760006109s) failure: assertion failed: match field not equal: $.status.readyReplicas had different values. expected 2 but found 1\ndeployment-exists (try 5 after 2.772416449s) ok: true, terminal: false\n=== RUN   TestMatches/matches/delete-deployment\n--- PASS: TestMatches (3.32s)\n    --- PASS: TestMatches/matches (3.30s)\n        --- PASS: TestMatches/matches/create-deployment (0.01s)\n        --- PASS: TestMatches/matches/deployment-exists (2.78s)\n        --- PASS: TestMatches/matches/delete-deployment (0.02s)\nPASS\nok  \tcommand-line-arguments\t3.683s\n```\n\nYou can see from the debug output above that `gdt` created the Deployment and\nthen did a `kube.get` for the `deployments/nginx` Deployment. Initially\n(attempt 1), the `assert.matches` assertion failed because the\n`status.readyReplicas` field was not present in the returned resource. `gdt`\nretried the `kube.get` call 4 more times (attempts 2-5), with attempts 2 and 3\nfailed the existence check for the `status.readyReplicas` field and attempt 4\nfailing the *value* check for the `status.readyReplicas` field being `1`\ninstead of the expected `2`. Finally, when the Deployment was completely rolled\nout, attempt 5 succeeded in all the `assert.matches` assertions.\n\n## Contributing and acknowledgements\n\n`gdt` was inspired by [Gabbi](https://github.com/cdent/gabbi), the excellent\nPython declarative testing framework. `gdt` tries to bring the same clear,\nconcise test definitions to the world of Go functional testing.\n\nThe Go gopher logo, from which gdt's logo was derived, was created by Renee\nFrench.\n\nContributions to `gdt` are welcomed! Feel free to open a Github issue or submit\na pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgdt-dev%2Fgdt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgdt-dev%2Fgdt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgdt-dev%2Fgdt/lists"}