{"id":18284612,"url":"https://github.com/libsv/go-bdd-common","last_synced_at":"2025-04-05T07:31:52.781Z","repository":{"id":37413735,"uuid":"409554152","full_name":"libsv/go-bdd-common","owner":"libsv","description":null,"archived":false,"fork":false,"pushed_at":"2024-11-22T10:57:15.000Z","size":9453,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T17:24:36.827Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/libsv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2021-09-23T10:56:34.000Z","updated_at":"2023-01-31T19:10:25.000Z","dependencies_parsed_at":"2023-02-18T05:45:55.972Z","dependency_job_id":"e5111659-6903-46a4-bb5e-25961c02a298","html_url":"https://github.com/libsv/go-bdd-common","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libsv%2Fgo-bdd-common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libsv%2Fgo-bdd-common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libsv%2Fgo-bdd-common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libsv%2Fgo-bdd-common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/libsv","download_url":"https://codeload.github.com/libsv/go-bdd-common/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247305876,"owners_count":20917198,"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":[],"created_at":"2024-11-05T13:14:09.958Z","updated_at":"2025-04-05T07:31:47.717Z","avatar_url":"https://github.com/libsv.png","language":"Go","funding_links":["https://github.com/sponsors/libsv"],"categories":[],"sub_categories":[],"readme":"# go-bdd-common\n\n[![Release](https://img.shields.io/github/release-pre/libsv/go-bdd-common.svg?logo=github\u0026style=flat\u0026v=1)](https://github.com/libsv/go-bdd-common/releases)\n[![Go](https://github.com/libsv/go-bt/actions/workflows/run-tests.yml/badge.svg?branch=master)](https://github.com/libsv/go-bt/actions/workflows/run-tests.yml)\n[![Go](https://github.com/libsv/go-bt/actions/workflows/run-tests.yml/badge.svg?event=pull_request)](https://github.com/libsv/go-bt/actions/workflows/run-tests.yml)\n\n[![Report](https://goreportcard.com/badge/github.com/libsv/go-bdd-common?style=flat\u0026v=1)](https://goreportcard.com/report/github.com/libsv/go-bdd-common)\n[![Go](https://img.shields.io/github/go-mod/go-version/libsv/go-bdd-common?v=1)](https://golang.org/)\n[![Sponsor](https://img.shields.io/badge/sponsor-libsv-181717.svg?logo=github\u0026style=flat\u0026v=3)](https://github.com/sponsors/libsv)\n[![Donate](https://img.shields.io/badge/donate-bitcoin-ff9900.svg?logo=bitcoin\u0026style=flat\u0026v=3)](https://gobitcoinsv.com/#sponsor)\n[![Mergify Status][mergify-status]][mergify]\n\n[mergify]: https://mergify.io\n[mergify-status]: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/libsv/go-bdd-common\u0026style=flat\n\u003cbr/\u003e\n\ngo-bdd-common is a generic [bdd](https://en.wikipedia.org/wiki/Behavior-driven_development) tool helping to write scalable tests for microservices applications. The framework is implemented in golang using [godog](https://github.com/cucumber/godog).\n\nIt runs within Docker Compose allowing you to setup and a tear down a deterministic known state environment within which to run your integration tests.\n\nIt supports 3 transports at the moment:\n\n* Http\n* Grpc\n* Kafka\n\nThese allow you to send and receive data to services and test the responses, headers and response codes they may return.\n\nIt uses templated json files for request data and can validate responses against json files.\n\n## To use\n\nIn order to use the library, add it to your project:\n\n```shell\n  go get github.com/libsv/go-bdd-common\n```\n\nWe recommend adding to the service you are testing to keep everything together and adding a `testing` folder at the root with an `integration` sub folder. This \nwill allow you to keep all your tests in one place and keep the integration tests separate.\n\n### Folder structure\n\nSetup the below folder structure\n\n* testing\n  * integration\n    * data (for json request files)\n    * features (for .feature files)\n    * responses (for json responses)\n    * env.yml (see below, allows you to setup real / mocked services)\n    * integration_test.go (test entry point)\n    \n### integration_test.go\n\nThis is the main entry point to the tests and is picked up when running go test, note the build flag at the top.\n\nThis ensures the tests are only ran when you explicitly want to run them, otherwise, when running unit tests the integration suite would also be spun up.\n\n```golang\n// +build godog\n\npackage integration\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\tcgodog \"github.com/libsv/go-bdd-common/godog\"\n)\n\nfunc TestMain(t *testing.M) {\n\texitCode := 1\n\tdefer func() { os.Exit(exitCode) }()\n\n\texitCode = cgodog.NewSuite(cgodog.Options{\n\t\tServiceName:        \"service-name\", // your service\n\t\tServicePort:        \":1234\", // your port\n\t\tInitBitcoinBackend: true, // do we want bitcoin node running\n\t\tInitS3:             true, // do we want to setup minio\n\t\tInitKafka:          true, // initialise kafka\n\t}).Run()\n}\n```\n\n## Environments\n\nYou can choose to run real or mocked services when running the integration tests.\n\nNOTE: these services must exist in the docker compose file you are using, if you supply a service that isn't present, it will fail.\n\nAt the root of your integration test folder, add an `env.yml` file.\n\nAn example is shown:\n\n```yaml\nreal:\n  - s3\n  - kafka\nmocks:\n  - this-service\n  - that-service\nvars:\n  - MY_ENV_VAR=true\n```\n\nNote the 3 sections:\n\n * real - actual services to run, these will be setup as docker containers\n * mocks - mockec services, we can setup predicates for these to return canned responses\n * vars - custom environment variables\n\n### Real Services \n\nThese services must exist in the docker compose file being used.\n\nBeing in this section means the tests will setup a container for this service and run the actual code.\n\nUseful if you *NEED* to run another service your service depends on like kafka, sql etc.\n\n### Mocking Services\n\nAs much as possible, you want to exercise the service being tested only, this is where you can then mock downstream dependencies.\n\nFor example, given the below dependency:\n\n  my-service -\u003e other-service\n\nWe want to test my-service but don't want to test other-service as this is a test for my-service.\n\nBy mocking other-service you ensure that a failure in other-service won't impact my-service.\n\nBUT by mocking, you can FORCE failures to check how my-service works. For example, for a request you could send a header or body data, setup the mock to return a 500 and test your servic ereacts properly.\n\n#### Example mock predicate\n\nAdd the mock .json files to the /mock folder.\n\nWe recommend a file per mocked service, but you can split these as required.\n\nThe server we used is [based off grpc-mock](https://github.com/Tigh-Gherr/grpc-mock).\n\nSome examples can be seen by following the above link.\n\nNOTE: only grpc mocking is supported right now.\n\n### Environment Variables\n\nThese are environment variables and they are passed ONLY to the service being tested.\n\n## Custom Steps\n\nThe pre-canned steps are documented below.\n\nYou can of course add your own specific step definitions as required.\n\nThese are hooked into the `cgodog.NewSuite(` call in `integration_tests.go`\n\nOur general steps should cover most scenarios, but of course there are edge cases and specifics that don't make sense here but you need.\n\n### Init Scenario\n\nThe hook is an initScenerio function, the signature is shown below and is ran before each scenario is ran:\n\n```go\nfunc initScenario(sCtx *godog.ScenarioContext, tCtx *cgodog.Context) \n```\n\nYou can then add to the initSuite call as shown:\n\n```go\n  cgodog.NewSuite(cgodog.Options{\n      ServiceName:         \"my-service\",\n      ServicePort:         \":1234\",\n      ScenarioInitializer: initScenario, // add function hook\n  }).Run())\n  \n  func initScenario(sCtx *godog.ScenarioContext, tCtx *cgodog.Context) {\n    // can also add some pre-scenario code that is ran before every scenario is ran\n  \n    ctx.Step(`^I make a RANDOM request to \"([^\"]*)\"$`, IMakeARANDOMRequestTo(testCtx))\n    \n  }\n\n  // IMakeARANDOMRequestTo \n  func IMakeARANDOMRequestTo(ctx *Context) func(string) error {\n    return func(proto string) error {\n      // implementation of step\n    }\n  }\n\n```\n\nYou can then add your new function to the feature files in the service.\n\n## Steps\n\nThere are a number of pre-canned steps ready to use, these should cover most basic tests required when\ntesting microservices.\n\n### GRPC\n\n| Step                                                  | Description                                                                                  | Example                                                                                                |\n|-------------------------------------------------------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|\n| I make a GRPC request to \"\"                           | Sends a GRPC request, with an empty body                                                     | I make a GRPC request to \"proto.MyService/GetThing\"                                                    |\n| I make a GRPC request to \"\" with JSON \"\"              | Sends a GRPC request, with a json body, read from data/{filename}.json                       | I make a GRPC request to \"proto.MyService/AddThing\" with JSON \"the_request\"                            |\n| I make a GRPC request to \"\" on port 1234 with JSON \"\" | Sends a GRPC request, with a json body, read from data/{filename}.json to a specific port    | I make a GRPC request to \"proto.MyService/AddThing\" on port 1234 with JSON \"the_request\"               |\n| the GRPC code should be xx                            | Ran after one of the above steps, will check the GRPC response code matches the one supplied | the GRPC code should be OK - codes are listed https://grpc.github.io/grpc/core/md_doc_statuscodes.html |\n\n### Http\n\n| Step                                                        | Description                                                                                                       | Example                                                                 |\n|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|\n| I make a (GET POST PATCH DELETE) request to \"\"              | Sends an HTTP request with the supplied method to the endpoint defined.                                           | I make a GET request to \"/api/v1/endpoint\"                              |\n| I make a (GET POST PATCH DELETE) request to \"\" with JSON \"\" | Sends an HTTP request with the supplied method to the endpoint defined with a request body defined in a JSON file | I make a POST request to \"/api/v1/endpoint\" with JSON \"my_request\"      |\n| the HTTP response code should be 000                        | Added after one of the above steps, will validate the http response code matches what we expect                   | the HTTP response code should be 201 - one of https://httpstatuses.com/ |\n\n### Bitcoin\n\n| Step                                                      | Description                                                                   | Example                              |\n|-----------------------------------------------------------|-------------------------------------------------------------------------------|--------------------------------------|\n| there (are is) ([0-9]+) bitcoin account(s)?$              | Will setup a bitcoin node with n accounts and assign them to ctx.BTC.Accounts | there are 4 bitcoin accounts         |\n| I send ([0-9.]+) BTC to account ([0-9]+)$                 | Sends the supplied about of bitcoin to the account with index                 | I send 0.001 BTC to account 1        |\n| account ([0-9]+) sends ([0-9.]+) BTC to account ([0-9]+)$ | Send n number of bitcoin to account index from account                        | account 0 sends 0.1 BTC to account 1 |\n| I send BTC to accounts:                                   | A table that defines account aliases and amount to top them up with           |                                      |\n\n#### BTC To Accounts\n\n```gherkin\nAnd I send BTC to accounts:\n| alias        | amount     |\n| unused1      | 2          |\n| unused2      | 3          |\n```\n\n### Transaction\n\n| Step                                                      | Description                                                                   | Example                              |\n|-----------------------------------------------------------|-------------------------------------------------------------------------------|--------------------------------------|\n| there (are is) ([0-9]+) bitcoin account(s)?$              | Will setup a bitcoin node with n accounts and assign them to ctx.BTC.Accounts | there are 4 bitcoin accounts         |\n| I send ([0-9.]+) BTC to account ([0-9]+)$                 | Sends the supplied about of bitcoin to the account with index                 | I send 0.001 BTC to account 1        |\n| account ([0-9]+) sends ([0-9.]+) BTC to account ([0-9]+)$ | Send n number of bitcoin to account index from account                        | account 0 sends 0.1 BTC to account 1 |\n| I send BTC to accounts:                                   | A table that defines account aliases and amount to top them up with           |                                      |\n\n### Database\n\nDatabase files should be stored under the /sql folder with the suffix .sql. The suffix does not need to be added to the name in the below steps\n\n| Step                                 | Description                                                                                 | Example                                     |\n|--------------------------------------|---------------------------------------------------------------------------------------------|---------------------------------------------|\n| the database is seeded with \"([^\"]*) | Run the named sql file against the database, useful for seeding a database with known state | Give the database is seeded with \"bad_data\" |\n\n### S3 / minio\n\nAny files content checks will look for a folder named s3 in the root of the Integration test folder. Unlike json and sql files, suffix is required here as there\ncould be different file content types we are checking.\n\n| Step                                                       | Description                                                                                                                           | Example                                                               |\n|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|\n| I delete from s3:                                          | Remove a list of file names from S3                                                                                                   | Give I delete from s3:  (full example below)                          |\n| the file \"([^\"]+)\" should exist in s3                      | Checks that a file with the key provided exists, this can be of the form of a file path                                               | And the file \"test/thing\" should exist in s3                          |\n| the file \"([^\"]+)\" in s3 contains the content of \"([^\"]+)\" | Checks that a file at path has content matching the file provided. These files should be added to an s3 folder and include the suffix | And the file \"test/thing\" in s3 contains the content of \"post_tx.txt\" |\n\n### Kafka\n\n| Step                                                          | Description                                                                                                                                                                                  | Example                                                                |\n|---------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|\n| I send a Kafka message to topic \"([^\"]*)\" with JSON \"([^\"]*)\" | Publishes the supplied JSON content to the kafka topic provided. Messages sent are given a unique identifier allowing the value to be checked by the below step as the identifier propagates | When I send a Kafka message to topic \"test.topic\" with JSON \"my_data\"  |\n| I listen to topic \"([^\"]*)\" and wait (\\d+) ms for our message | Sets up a listener on the provided topic and can be given a configurable wait.                                                                                                               | And I listen to topic \"test.response\" and wait 5000 ms for our message |\n\n### General\n\n| Step                                      | Description                                                                                                | Example                                                                                                              |\n|-------------------------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|\n| the headers:                              | Adds key value pairs to all outgoing requests following                                                    | Given the headers: (example below)                                                                                   |\n| I delete the headers:                     | A list of header names to remove                                                                           | I delete the headers:                                                                                                |\n| the template values:                      | Key value pairs of values to write to use in request or responses                                          | Given the template values:                                                                                           |\n| I store from the response for templating: | Used for responses, can supply a JSON path and a name to give it, this can then be referenced in responses | And I store from the response for templating:                                                                        |\n| the data should match JSON \"([^\"]*)\"      | Will check a response (from http, grpc or kafka) matches the json at the file named.                       | Then I listen to topic \"test.accepted\" and wait 2000 ms for our message And the data should match JSON \"my_expected\" |\n| I wait for (\\d+) second(s)                | Pauses the current scenario for n seconds                                                                  | And I wait for 1 second                                                                                              |\n\n#### the headers:\nExample for the headers:\n\n```gherkin\n    And the headers:\n      | key           | val |\n      | header1 | abc |\n      | header2 | integration-tx-broadcast |\n```\n\n#### I delete the headers:\n\nExample\n\n```gherkin\nAnd I delete the headers:\n      | key     |\n      | header1 |\n      | header2 |\n```\n\n#### the template values:\n\nExample\n\nThis will write the value 100 to the variable named balance as shown:\n\n```gherkin\n    Given I fund 100 satoshis\n    And the template values:\n      | key      | val |\n      | balance  | 100 |\n```\n\nThis variable can then be referenced in data or response json files like this:\n\n```json\n{\n  \"myprop\": \"test\",\n  \"balance\": \"{{ .balance }}\"\n  \n}\n```\nIf you need to check the same response template over and over with different values, this is a convenient way of doing it\n\n#### I store from the response for templating:\n\n```gherkin\n    And I store from the response for templating:\n      | jsonpath | name |\n      | body.TxBytes | transaction |\n```\n\nThis stores a value in the json response names TxBytes to a new variable called transaction.\n\nThis can then be referenced in json files as shown:\n\n```json\n{\n  \"myprop\": \"test\",\n  \"tx\": \"{{ .transaction }}\"\n  \n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibsv%2Fgo-bdd-common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flibsv%2Fgo-bdd-common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibsv%2Fgo-bdd-common/lists"}