diff --git a/Makefile b/Makefile index d294f44..b92938c 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,38 @@ -#git_dir is path to default git folder with repository +.PHONY: tests + +# git_dir is path to default git folder with repository git_dir=./.git -#github_actions_dir is path to github actions directory with workflow +# github_actions_dir is path to github actions directory with workflow github_actions_dir=./.github -#features_dir is path to directory with features +# features_dir is path to directory with features features_dir=./features -#gitignore_path is path to .gitignore file +# gitignore_path is path to .gitignore file gitignore_path=./.gitignore -#assets_dir is path to directory with temporary assets +# assets_dir is path to directory with temporary assets assets_dir=./assets -#env creates .env file and populates it with default values +# env creates .env file and populates it with default values env: touch .env echo "GODOG_DEBUG=false\nGODOG_MY_APP_URL=http://localhost:1234\nGODOG_JSON_SCHEMA_DIR=./assets/test_server/doc/schema" >> .env -#download-dependencies download go packages and godog binary +# download-dependencies download go packages and godog binary download-dependencies: go mod download go install github.com/cucumber/godog/cmd/godog@v0.12.4 -#clean removes directories with binaries, git repository, github actions workflow and test suite +# clean removes directories with binaries, git repository, github actions workflow and test suite clean: rm -rf ${git_dir} rm -rf ${github_actions_dir} rm -rf ${assets_dir} rm -rf ${features_dir} - rm ${gitignore_path} \ No newline at end of file + rm ${gitignore_path} + +# tests run all tests concurrently, server should be run manually +tests: + godog --format=progress --concurrency=2 --random \ No newline at end of file diff --git a/README.MD b/README.MD index 1becec0..5e1f4b5 100644 --- a/README.MD +++ b/README.MD @@ -1,29 +1,28 @@ ![godog-example-setup](https://github.com/pawelWritesCode/godog-example-setup/actions/workflows/go.yml/badge.svg) -# Example godog framework setup with step library gdutils +# Godog framework developed to test HTTP(s) API. ## Overview: -This repository contains skeleton that allow to write End-2-End tests for HTTP API with use of framework [godog](https://github.com/cucumber/godog) -that implements 🥒[gherkin/cucumber](https://cucumber.io/docs/gherkin/) syntax using step library [gdutils](https://github.com/pawelWritesCode/gdutils). +This repository contains skeleton that allow to write End-2-End tests for HTTP(s) API with use of framework [godog](https://github.com/cucumber/godog) +that implements 🥒[cucumber/gherkin](https://cucumber.io/docs/gherkin/) syntax using step library [gdutils](https://github.com/pawelWritesCode/gdutils). ## Why ? -**No more** long and time-consuming framework setup. This project **cuts initial time** & allows bootstrap e2e test framework with **plenty of -utility methods** (_for testing HTTP(s) API using JSON/YAML/XML_) in just few steps. Just grab it and write tests right away. +This project **cuts initial time** & allows bootstrap e2e test framework with **plenty of +utility methods** in just few steps. Just grab it and write tests right away! Benefits: -* **short time** from **[setup](https://github.com/pawelWritesCode/godog-example-setup/wiki/Set-up#clone-repository) - to writing** actual E2E tests, * [35+ well-documented, coupled in logical groups steps](https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps) useful for testing HTTP(s) API, * support for using templated values as in [text/template](https://pkg.go.dev/text/template) package, * support for querying nodes with different path engines ([oliveagle](https://github.com/oliveagle/jsonpath), [qjson](https://github.com/pawelWritesCode/qjson) - JSON, [go-yaml](https://github.com/goccy/go-yaml) - YAML, [antchfx](https://github.com/antchfx/xmlquery) - XML), -* support for sending _multipart/form-data_ forms with file in it, -* rich debugging possibilities - various ways to debug tests, -* customisable - ability to [replace](https://github.com/pawelWritesCode/godog-example-setup/blob/main/main_test.go#L58) utility services with your own implementations, -* supports custom [steps development](https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps-development) through [exported utility services](https://github.com/pawelWritesCode/gdutils/blob/master/state.go#L18). +* support for sending _multipart/form-data_ forms with file in it, +* developed with debugging in mind, +* customisable through ability to [replace](https://github.com/pawelWritesCode/godog-example-setup/blob/main/main_test.go#L58) utility services with your own implementations, +* supports [custom steps development](https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps-development). ## Example -#### [Click here 👆 to see more tests using JSON/YAML/XML](https://github.com/pawelWritesCode/godog-example-setup/blob/main/features/test_server) +#### [👆 Click here to see more tests using JSON/YAML/XML](https://github.com/pawelWritesCode/godog-example-setup/blob/main/features/test_server) ```cucumber Feature: Adding new user diff --git a/defs/scenario.go b/defs/scenario.go index e3755c1..0b43cd4 100644 --- a/defs/scenario.go +++ b/defs/scenario.go @@ -14,10 +14,10 @@ import ( "github.com/pawelWritesCode/gdutils/pkg/timeutils" ) -// Scenario represents Scenario unit in context of godog framework. +// Scenario is entity that contains utility services and holds methods used behind godog steps. type Scenario struct { - // State is responsible for data flow inside one scenario. - State *gdutils.State + // APIContext holds utility services and methods for working with HTTP(s) API. + APIContext *gdutils.APIContext } // IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs creates random runes generator func using provided charset. @@ -27,15 +27,15 @@ func (s *Scenario) IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs(from, switch strings.ToLower(charset) { case "ascii": - generateWordFunc = s.State.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetASCII) + generateWordFunc = s.APIContext.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetASCII) case "unicode": - generateWordFunc = s.State.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetUnicode) + generateWordFunc = s.APIContext.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetUnicode) case "polish": - generateWordFunc = s.State.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetPolish) + generateWordFunc = s.APIContext.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetPolish) case "english": - generateWordFunc = s.State.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetEnglish) + generateWordFunc = s.APIContext.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetEnglish) case "russian": - generateWordFunc = s.State.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetRussian) + generateWordFunc = s.APIContext.IGenerateARandomRunesInTheRangeToAndSaveItAs(stringutils.CharsetRussian) default: return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian", charset) } @@ -48,9 +48,9 @@ func (s *Scenario) IGenerateARandomRunesOfLengthWithCharactersAndSaveItAs(from, func (s *Scenario) IGenerateARandomNumberInTheRangeFromToAndSaveItAs(numberType string, from, to int, cacheKey string) error { switch strings.ToLower(numberType) { case "float": - return s.State.IGenerateARandomFloatInTheRangeToAndSaveItAs(from, to, cacheKey) + return s.APIContext.IGenerateARandomFloatInTheRangeToAndSaveItAs(from, to, cacheKey) case "int": - return s.State.IGenerateARandomIntInTheRangeToAndSaveItAs(from, to, cacheKey) + return s.APIContext.IGenerateARandomIntInTheRangeToAndSaveItAs(from, to, cacheKey) default: return fmt.Errorf("unknown type %s, available: int, float", numberType) } @@ -63,15 +63,15 @@ func (s *Scenario) IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(minW var generateSentenceFunc func(from, to int, cacheKey string) error switch strings.ToLower(charset) { case "ascii": - generateSentenceFunc = s.State.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetASCII, minWordLength, maxWordLength) + generateSentenceFunc = s.APIContext.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetASCII, minWordLength, maxWordLength) case "unicode": - generateSentenceFunc = s.State.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetUnicode, minWordLength, maxWordLength) + generateSentenceFunc = s.APIContext.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetUnicode, minWordLength, maxWordLength) case "polish": - generateSentenceFunc = s.State.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetPolish, minWordLength, maxWordLength) + generateSentenceFunc = s.APIContext.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetPolish, minWordLength, maxWordLength) case "english": - generateSentenceFunc = s.State.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetEnglish, minWordLength, maxWordLength) + generateSentenceFunc = s.APIContext.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetEnglish, minWordLength, maxWordLength) case "russian": - generateSentenceFunc = s.State.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetRussian, minWordLength, maxWordLength) + generateSentenceFunc = s.APIContext.IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(stringutils.CharsetRussian, minWordLength, maxWordLength) default: return fmt.Errorf("unknown charset '%s', available: ascii, unicode, polish, english, russian", charset) } @@ -82,7 +82,7 @@ func (s *Scenario) IGenerateARandomSentenceInTheRangeFromToWordsAndSaveItAs(minW // func (s *Scenario) IGenerateRandomBoolValueAndSaveItAs(cacheKey string) error { - s.State.Cache.Save(cacheKey, rand.Intn(2) == 0) + s.APIContext.Cache.Save(cacheKey, rand.Intn(2) == 0) return nil } @@ -95,7 +95,7 @@ func (s *Scenario) IGenerateCurrentTimeAndTravelByAndSaveItAs(timeDirection, tim return err } - return s.State.IGenerateCurrentTimeAndTravelByAndSaveItAs(timeutils.TimeDirection(timeDirection), duration, cacheKey) + return s.APIContext.IGenerateCurrentTimeAndTravelByAndSaveItAs(timeutils.TimeDirection(timeDirection), duration, cacheKey) } /* @@ -107,24 +107,24 @@ func (s *Scenario) IGenerateCurrentTimeAndTravelByAndSaveItAs(timeDirection, tim in JSON or YAML format with keys "body" and "headers". */ func (s *Scenario) ISendRequestToWithBodyAndHeaders(method, urlTemplate string, reqBody *godog.DocString) error { - return s.State.ISendRequestToWithBodyAndHeaders(method, urlTemplate, reqBody.Content) + return s.APIContext.ISendRequestToWithBodyAndHeaders(method, urlTemplate, reqBody.Content) } // IPrepareNewRequestToAndSaveItAs prepares new request and saves it in cache under cacheKey. func (s Scenario) IPrepareNewRequestToAndSaveItAs(method, urlTemplate, cacheKey string) error { - return s.State.IPrepareNewRequestToAndSaveItAs(method, urlTemplate, cacheKey) + return s.APIContext.IPrepareNewRequestToAndSaveItAs(method, urlTemplate, cacheKey) } // ISetFollowingHeadersForPreparedRequest sets provided headers for previously prepared request. -// incoming data should be in format acceptable by injected s.State.Deserializer +// incoming data should be in format acceptable by injected s.APIContext.Deserializer func (s Scenario) ISetFollowingHeadersForPreparedRequest(cacheKey string, headersTemplate *godog.DocString) error { - return s.State.ISetFollowingHeadersForPreparedRequest(cacheKey, headersTemplate.Content) + return s.APIContext.ISetFollowingHeadersForPreparedRequest(cacheKey, headersTemplate.Content) } // ISetFollowingCookiesForPreparedRequest sets cookies for previously prepared request // cookies template should be YAML or JSON deserializable on []http.Cookie func (s Scenario) ISetFollowingCookiesForPreparedRequest(cacheKey string, cookies *godog.DocString) error { - return s.State.ISetFollowingCookiesForPreparedRequest(cacheKey, cookies.Content) + return s.APIContext.ISetFollowingCookiesForPreparedRequest(cacheKey, cookies.Content) } /* @@ -133,83 +133,83 @@ func (s Scenario) ISetFollowingCookiesForPreparedRequest(cacheKey string, cookie formTemplate should be YAML or JSON deserializable on map[string]string. */ func (s Scenario) ISetFollowingFormForPreparedRequest(cacheKey string, formTemplate *godog.DocString) error { - return s.State.ISetFollowingFormForPreparedRequest(cacheKey, formTemplate.Content) + return s.APIContext.ISetFollowingFormForPreparedRequest(cacheKey, formTemplate.Content) } // ISetFollowingBodyForPreparedRequest sets body for previously prepared request. // bodyTemplate may be in any format and accepts template values. func (s Scenario) ISetFollowingBodyForPreparedRequest(cacheKey string, bodyTemplate *godog.DocString) error { - return s.State.ISetFollowingBodyForPreparedRequest(cacheKey, bodyTemplate.Content) + return s.APIContext.ISetFollowingBodyForPreparedRequest(cacheKey, bodyTemplate.Content) } // ISendRequest sends previously prepared HTTP(s) request. func (s Scenario) ISendRequest(cacheKey string) error { - return s.State.ISendRequest(cacheKey) + return s.APIContext.ISendRequest(cacheKey) } // TheResponseShouldHaveHeader checks whether last HTTP response has given header. func (s *Scenario) TheResponseShouldHaveHeader(name string) error { - return s.State.TheResponseShouldHaveHeader(name) + return s.APIContext.TheResponseShouldHaveHeader(name) } // TheResponseShouldHaveHeaderOfValue checks whether last HTTP response has given header with provided value. func (s *Scenario) TheResponseShouldHaveHeaderOfValue(name, value string) error { - return s.State.TheResponseShouldHaveHeaderOfValue(name, value) + return s.APIContext.TheResponseShouldHaveHeaderOfValue(name, value) } // TheResponseStatusCodeShouldBe checks last response status code. func (s *Scenario) TheResponseStatusCodeShouldBe(code int) error { - return s.State.TheResponseStatusCodeShouldBe(code) + return s.APIContext.TheResponseStatusCodeShouldBe(code) } // TheResponseShouldHaveNode checks whether last response body contains given node. // expr should be valid according to injected PathFinder for given data format -func (s *Scenario) TheResponseShouldHaveNode(dataFormat, expr string) error { - return s.State.TheResponseShouldHaveNode(format.DataFormat(dataFormat), expr) +func (s *Scenario) TheResponseShouldHaveNode(dataFormat, exprTemplate string) error { + return s.APIContext.TheResponseShouldHaveNode(format.DataFormat(dataFormat), exprTemplate) } // TheNodeShouldBeOfValue compares json node value from expression to expected by user dataValue of given by user dataType // Available data types are listed in switch section in each case directive. // expr should be valid according to injected PathFinder for provided dataFormat. -func (s *Scenario) TheNodeShouldBeOfValue(dataFormat, expr, dataType, dataValue string) error { - return s.State.TheNodeShouldBeOfValue(format.DataFormat(dataFormat), expr, dataType, dataValue) +func (s *Scenario) TheNodeShouldBeOfValue(dataFormat, exprTemplate, dataType, dataValue string) error { + return s.APIContext.TheNodeShouldBeOfValue(format.DataFormat(dataFormat), exprTemplate, dataType, dataValue) } // TheNodeShouldBeSliceOfLength checks whether given key is slice and has given length // expr should be valid according to injected PathFinder for provided dataFormat -func (s *Scenario) TheNodeShouldBeSliceOfLength(dataFormat, expr string, length int) error { - return s.State.TheNodeShouldBeSliceOfLength(format.DataFormat(dataFormat), expr, length) +func (s *Scenario) TheNodeShouldBeSliceOfLength(dataFormat, exprTemplate string, length int) error { + return s.APIContext.TheNodeShouldBeSliceOfLength(format.DataFormat(dataFormat), exprTemplate, length) } // TheNodeShouldBe checks whether node from last response body is of provided type // goType may be one of: nil, string, int, float, bool, map, slice // expr should be valid according to injected PathResolver -func (s *Scenario) TheNodeShouldBe(dataFormat, expr, goType string) error { - return s.State.TheNodeShouldBe(format.DataFormat(dataFormat), expr, goType) +func (s *Scenario) TheNodeShouldBe(dataFormat, exprTemplate, goType string) error { + return s.APIContext.TheNodeShouldBe(format.DataFormat(dataFormat), exprTemplate, goType) } // TheNodeShouldNotBe checks whether node from last response body is not of provided type. // goType may be one of: nil, string, int, float, bool, map, slice, // expr should be valid according to injected PathFinder for given data format. -func (s *Scenario) TheNodeShouldNotBe(dataFormat, expr, goType string) error { - return s.State.TheNodeShouldNotBe(format.DataFormat(dataFormat), expr, goType) +func (s *Scenario) TheNodeShouldNotBe(dataFormat, exprTemplate, goType string) error { + return s.APIContext.TheNodeShouldNotBe(format.DataFormat(dataFormat), exprTemplate, goType) } // TheResponseShouldHaveNodes checks whether last request body has keys defined in string separated by comma // nodeExprs should be valid according to injected PathFinder expressions separated by comma (,) func (s *Scenario) TheResponseShouldHaveNodes(dataFormat, nodesExpr string) error { - return s.State.TheResponseShouldHaveNodes(format.DataFormat(dataFormat), nodesExpr) + return s.APIContext.TheResponseShouldHaveNodes(format.DataFormat(dataFormat), nodesExpr) } // TheNodeShouldMatchRegExp checks whether last response body node matches provided regExp. -func (s *Scenario) TheNodeShouldMatchRegExp(dataFormat, expr, regExpTemplate string) error { - return s.State.TheNodeShouldMatchRegExp(format.DataFormat(dataFormat), expr, regExpTemplate) +func (s *Scenario) TheNodeShouldMatchRegExp(dataFormat, exprTemplate, regExpTemplate string) error { + return s.APIContext.TheNodeShouldMatchRegExp(format.DataFormat(dataFormat), exprTemplate, regExpTemplate) } // TheResponseBodyShouldHaveFormat checks whether last response body has given data format. // Available data formats are listed in format package. func (s *Scenario) TheResponseBodyShouldHaveFormat(dataFormat string) error { - return s.State.TheResponseBodyShouldHaveFormat(format.DataFormat(dataFormat)) + return s.APIContext.TheResponseBodyShouldHaveFormat(format.DataFormat(dataFormat)) } /* @@ -219,13 +219,13 @@ func (s *Scenario) TheResponseBodyShouldHaveFormat(dataFormat string) error { - relative path from JSON schema's dir which was passed in main_test to initialize *Scenario struct instance, - URL */ -func (s *Scenario) IValidateLastResponseBodyWithSchema(reference string) error { - return s.State.IValidateLastResponseBodyWithSchemaReference(reference) +func (s *Scenario) IValidateLastResponseBodyWithSchema(referenceTemplate string) error { + return s.APIContext.IValidateLastResponseBodyWithSchemaReference(referenceTemplate) } // IValidateLastResponseBodyWithFollowingSchema validates last response body against JSON schema provided by user. func (s *Scenario) IValidateLastResponseBodyWithFollowingSchema(schemaBytes *godog.DocString) error { - return s.State.IValidateLastResponseBodyWithSchemaString(schemaBytes.Content) + return s.APIContext.IValidateLastResponseBodyWithSchemaString(schemaBytes.Content) } /* @@ -239,42 +239,42 @@ func (s *Scenario) TimeBetweenLastHTTPRequestResponseShouldBeLessThanOrEqualTo(t return err } - return s.State.TimeBetweenLastHTTPRequestResponseShouldBeLessThanOrEqualTo(duration) + return s.APIContext.TimeBetweenLastHTTPRequestResponseShouldBeLessThanOrEqualTo(duration) } // TheResponseShouldHaveCookie checks whether last HTTP(s) response has cookie of given name. func (s *Scenario) TheResponseShouldHaveCookie(name string) error { - return s.State.TheResponseShouldHaveCookie(name) + return s.APIContext.TheResponseShouldHaveCookie(name) } // TheResponseShouldHaveCookieOfValue checks whether last HTTP(s) response has cookie of given name and value. func (s *Scenario) TheResponseShouldHaveCookieOfValue(name, valueTemplate string) error { - return s.State.TheResponseShouldHaveCookieOfValue(name, valueTemplate) + return s.APIContext.TheResponseShouldHaveCookieOfValue(name, valueTemplate) } // IValidateNodeWithSchemaReference validates last response body node against schema as provided in reference -func (s *Scenario) IValidateNodeWithSchemaReference(dataFormat, expr, reference string) error { - return s.State.IValidateNodeWithSchemaReference(format.DataFormat(dataFormat), expr, reference) +func (s *Scenario) IValidateNodeWithSchemaReference(dataFormat, exprTemplate, referenceTemplate string) error { + return s.APIContext.IValidateNodeWithSchemaReference(format.DataFormat(dataFormat), exprTemplate, referenceTemplate) } // IValidateNodeWithSchemaString validates last response body JSON node against schema -func (s *Scenario) IValidateNodeWithSchemaString(dataFormat, expr string, jsonSchema *godog.DocString) error { - return s.State.IValidateNodeWithSchemaString(format.DataFormat(dataFormat), expr, jsonSchema.Content) +func (s *Scenario) IValidateNodeWithSchemaString(dataFormat, exprTemplate string, schemaTemplate *godog.DocString) error { + return s.APIContext.IValidateNodeWithSchemaString(format.DataFormat(dataFormat), exprTemplate, schemaTemplate.Content) } // ISaveAs saves into cache arbitrary passed value -func (s *Scenario) ISaveAs(value, cacheKey string) error { - return s.State.ISaveAs(value, cacheKey) +func (s *Scenario) ISaveAs(valueTemplate, cacheKey string) error { + return s.APIContext.ISaveAs(valueTemplate, cacheKey) } // ISaveFromTheLastResponseNodeAs saves from last response json node under given cache key. -func (s *Scenario) ISaveFromTheLastResponseNodeAs(dataFormat, expr, cacheKey string) error { - return s.State.ISaveFromTheLastResponseNodeAs(format.DataFormat(dataFormat), expr, cacheKey) +func (s *Scenario) ISaveFromTheLastResponseNodeAs(dataFormat, exprTemplate, cacheKey string) error { + return s.APIContext.ISaveFromTheLastResponseNodeAs(format.DataFormat(dataFormat), exprTemplate, cacheKey) } // IPrintLastResponseBody prints response body from last scenario request func (s *Scenario) IPrintLastResponseBody() error { - return s.State.IPrintLastResponseBody() + return s.APIContext.IPrintLastResponseBody() } /* @@ -288,17 +288,17 @@ func (s *Scenario) IWait(timeInterval string) error { return err } - return s.State.IWait(duration) + return s.APIContext.IWait(duration) } // IStartDebugMode starts debugging mode func (s *Scenario) IStartDebugMode() error { - return s.State.IStartDebugMode() + return s.APIContext.IStartDebugMode() } // IStopDebugMode stops debugging mode func (s *Scenario) IStopDebugMode() error { - return s.State.IStopDebugMode() + return s.APIContext.IStopDebugMode() } // IStopScenarioExecution stops scenario execution diff --git a/features/test_server/json/create.feature b/features/test_server/json/create.feature index a55600b..898be4f 100644 --- a/features/test_server/json/create.feature +++ b/features/test_server/json/create.feature @@ -18,7 +18,6 @@ Feature: Adding new user Given I generate current time and travel "backward" "240h" in time and save it as "MEET_DATE" Given I save "application/json" as "CONTENT_TYPE_JSON" - @wip Scenario: Successfully create user v1 As application user I would like to create new account diff --git a/features/test_server/yaml/replace.feature b/features/test_server/yaml/replace.feature index b5635dc..1c461d0 100644 --- a/features/test_server/yaml/replace.feature +++ b/features/test_server/yaml/replace.feature @@ -84,8 +84,6 @@ Feature: Replacing single user account. } """ Then the response status code should be 200 - And the response should have header "Content-Type" of value "application/x-yaml" - And the response body should have format "YAML" And time between last request and response should be less than or equal to "2s" #--------------------------------------------------------------------------------------------------- diff --git a/go.mod b/go.mod index 951238f..186af53 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/cucumber/godog v0.12.4 github.com/joho/godotenv v1.4.0 - github.com/pawelWritesCode/gdutils v0.13.1 + github.com/pawelWritesCode/gdutils v0.14.1 github.com/spf13/pflag v1.0.5 ) diff --git a/go.sum b/go.sum index ec4d754..c924a15 100644 --- a/go.sum +++ b/go.sum @@ -180,8 +180,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pawelWritesCode/gdutils v0.13.1 h1:03BhM9dLPQtYfZCSlUdGcYdgLWiTQTJQ+DR1JnnN6Mk= -github.com/pawelWritesCode/gdutils v0.13.1/go.mod h1:EN3UFzXecxhUGFEA15KIm+dTL5bBtJ6OMa5RQ+sifjg= +github.com/pawelWritesCode/gdutils v0.14.1 h1:Nk/g82PGJKrwDgiDFql1ioPpKku3h418pWwOPdKHUxI= +github.com/pawelWritesCode/gdutils v0.14.1/go.mod h1:EN3UFzXecxhUGFEA15KIm+dTL5bBtJ6OMa5RQ+sifjg= github.com/pawelWritesCode/qjson v1.0.1 h1:MreBuXjjQj2XROgrGK5e9rWDiwLzDO4TyMyo8IvYP9M= github.com/pawelWritesCode/qjson v1.0.1/go.mod h1:BBj5FLhYUYGE8lNCKdz+MjJab+2fFcs+s9NFDDFjjnk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/main_test.go b/main_test.go index ef3cbfd..b86edab 100644 --- a/main_test.go +++ b/main_test.go @@ -40,7 +40,6 @@ func init() { func TestMain(m *testing.M) { pflag.Parse() opt.Paths = pflag.Args() - status := godog.TestSuite{Name: "godogs", ScenarioInitializer: InitializeScenario, Options: &opt}.Run() os.Exit(status) @@ -52,40 +51,32 @@ func InitializeScenario(ctx *godog.ScenarioContext) { checkErr(err) /* - Variable scenario represents godog scenario. - Scenario has State field with plenty of utility services useful to create godog steps. - - If you would like to replace any of default state service with your own, do following: - - Let's assume, you want to replace default debugger with your own. Field scenario.State.Debugger has interface - type of debugger.Debugger. Create your own struct, implement on it interface debugger.Debugger and then, use - proper setter method to inject it into scenario.State. In this example it would be: scenario.State.SetDebugger. + scenario is entity that contains utility services and holds methods used behind godog steps. - In another example you may want to use your own http.Client. Then, create it and inject it to State with - scenario.State.SetRequestDoer setter. + If you would like to replace any of default state's utility services with your own, read: + https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps-development#utilityServices */ - scenario := defs.Scenario{State: gdutils.NewDefaultState(isDebug, path.Join(wd, os.Getenv(envJsonSchemaDir)))} + scenario := defs.Scenario{APIContext: gdutils.NewDefaultAPIContext(isDebug, path.Join(wd, os.Getenv(envJsonSchemaDir)))} ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { - scenario.State.ResetState(isDebug) + scenario.APIContext.ResetState(isDebug) - // Here you can define more scenario-scoped values using scenario.State.Cache.Save() method - scenario.State.Cache.Save("MY_APP_URL", os.Getenv(envMyAppURL)) - scenario.State.Cache.Save("CWD", wd) // current working directory - full OS path to this file + // Here you can define more scenario-scoped values using scenario.APIContext.Cache.Save() method + scenario.APIContext.Cache.Save("MY_APP_URL", os.Getenv(envMyAppURL)) + scenario.APIContext.Cache.Save("CWD", wd) // current working directory - full OS path to this file return ctx, nil }) - // Following code maps sentences to methods (define steps). To learn more on each step see + // Following declarations maps sentences to methods (define steps). To learn more on each step see // https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps /* |---------------------------------------------------------------------------------------------------------------- - | Random data generation + | Random data generation - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#data-generation |---------------------------------------------------------------------------------------------------------------- | - | This section contains utility methods for random data generation. - | Those methods contains creation of + | This section contains utility methods for random data generation. Those methods contain creation of | - random length runes of ASCII/UNICODE/polish/english/russian characters, | - random length sentence of ASCII/UNICODE/polish/english/russian words, | - int/float from provided range, @@ -102,11 +93,10 @@ func InitializeScenario(ctx *godog.ScenarioContext) { /* |---------------------------------------------------------------------------------------------------------------- - | Sending HTTP(s) requests + | Sending HTTP(s) requests - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#sending-https-requests |---------------------------------------------------------------------------------------------------------------- | | This section contains methods for preparing and sending HTTP(s) requests. - | | You can use one of two ways to send HTTP(s) request: | | First, composed but less customisable: @@ -132,7 +122,7 @@ func InitializeScenario(ctx *godog.ScenarioContext) { /* |---------------------------------------------------------------------------------------------------------------- - | Assertions + | Assertions - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#assertions |---------------------------------------------------------------------------------------------------------------- | | This section contains assertions against last HTTP(s) responses, especially: @@ -143,23 +133,18 @@ func InitializeScenario(ctx *godog.ScenarioContext) { | - time between request - response. | | Every argument following immediately after word "node" or "nodes" - | should have syntax acceptable by one of json-path libraries: - | https://github.com/pawelWritesCode/qjson (JSON) - | https://github.com/oliveagle/jsonpath (JSON) + | should have syntax acceptable by one of json-path libraries and may contain template values: + | https://github.com/pawelWritesCode/qjson or https://github.com/oliveagle/jsonpath (JSON) | https://github.com/goccy/go-yaml (YAML) | https://github.com/antchfx/xmlquery (XML) | | Method "the response should have nodes" accepts list of nodes, | separated with comma ",". For example: "data[0].user, $.data[1].user, data". | - | Every method, that ends with 'of value "([^"]*)"' accepts fixed values or - | template values. Template values are references to previously saved values - | in scenario cache. - | For example, after generating random string and saving it under "USER_NAME", - | argument of this method may be: "{{.USER_NAME}}" - | | Argument in method starting with 'time between ...' should be string valid for | golang standard library time.ParseDuration func, for example: 3s, 1h, 30ms + | + | Most of the methods accepts template values in their arguments. */ ctx.Step(`^the response should have header "([^"]*)"$`, scenario.TheResponseShouldHaveHeader) ctx.Step(`^the response should have header "([^"]*)" of value "([^"]*)"$`, scenario.TheResponseShouldHaveHeaderOfValue) @@ -177,8 +162,8 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^the "(JSON|YAML)" node "([^"]*)" should be "(nil|string|int|float|bool|map|slice)"$`, scenario.TheNodeShouldBe) ctx.Step(`^the "(JSON|YAML)" node "([^"]*)" should not be "(nil|string|int|float|bool|map|slice)"$`, scenario.TheNodeShouldNotBe) ctx.Step(`^the "(JSON|YAML|XML)" node "([^"]*)" should match regExp "([^"]*)"$`, scenario.TheNodeShouldMatchRegExp) - ctx.Step(`^the "(JSON|YAML)" node "([^"]*)" should be valid according to schema "([^"]*)"$`, scenario.IValidateNodeWithSchemaReference) - ctx.Step(`^the "(JSON|YAML)" node "([^"]*)" should be valid according to schema:$`, scenario.IValidateNodeWithSchemaString) + ctx.Step(`^the "(JSON)" node "([^"]*)" should be valid according to schema "([^"]*)"$`, scenario.IValidateNodeWithSchemaReference) + ctx.Step(`^the "(JSON)" node "([^"]*)" should be valid according to schema:$`, scenario.IValidateNodeWithSchemaString) ctx.Step(`^the response body should be valid according to schema "([^"]*)"$`, scenario.IValidateLastResponseBodyWithSchema) ctx.Step(`^the response body should be valid according to schema:$`, scenario.IValidateLastResponseBodyWithFollowingSchema) @@ -188,15 +173,14 @@ func InitializeScenario(ctx *godog.ScenarioContext) { /* |---------------------------------------------------------------------------------------------------------------- - | Preserving data + | Preserving data - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#preserving-data |---------------------------------------------------------------------------------------------------------------- | | This section contains method for preserving data in scenario cache | | Argument following immediately after word "node" - | should have syntax acceptable by one of path libraries: - | https://github.com/pawelWritesCode/qjson (JSON) - | https://github.com/oliveagle/jsonpath (JSON) + | should have syntax acceptable by one of path libraries and may contain template values: + | https://github.com/pawelWritesCode/qjson or https://github.com/oliveagle/jsonpath (JSON) | https://github.com/goccy/go-yaml (YAML) | https://github.com/antchfx/xmlquery (XML) */ @@ -205,29 +189,27 @@ func InitializeScenario(ctx *godog.ScenarioContext) { /* |---------------------------------------------------------------------------------------------------------------- - | Debugging + | Debugging - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#debugging |---------------------------------------------------------------------------------------------------------------- | | This section contains methods that are useful for debugging during test creation phase. */ - ctx.Step(`^I stop scenario execution$`, scenario.IStopScenarioExecution) - ctx.Step(`^I print last response body$`, scenario.IPrintLastResponseBody) - ctx.Step(`^I start debug mode$`, scenario.IStartDebugMode) ctx.Step(`^I stop debug mode$`, scenario.IStopDebugMode) /* |---------------------------------------------------------------------------------------------------------------- - | Flow control + | Flow control - https://github.com/pawelWritesCode/godog-example-setup/wiki/Steps#flow-control |---------------------------------------------------------------------------------------------------------------- | - | This section contains methods for control scenario flow + | This section contains methods for control scenario flow. | | Argument in method 'I wait ([^"]*)"' should be string valid for | golang standard library time.ParseDuration func, for example: 3s, 1h, 30ms */ ctx.Step(`^I wait "([^"]*)"`, scenario.IWait) + ctx.Step(`^I stop scenario execution$`, scenario.IStopScenarioExecution) } // checkErr checks error and log if found.