diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c59dd..75978cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# Release 1.15.0 +Adding support in the paths to support templates. +If the path contains something like: +``` +/api/v1/${dynamicElement}/ +``` +In this case, the `dynamicElement` will be replaced, if it exists in the ScenarioContext. + +Support for adding static key/value pairs to the context: +- [Set a static value to the context](README.md#set-a-static-value-to-the-context) +- [Set multiple static values to the context](README.md#set-multiple-static-values-to-the-context) + # Release 1.14.0 Adding support for the JSON path to the "I set the value of" sentence. diff --git a/README.md b/README.md index 397cba5..c1f3198 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ See [Changelog](CHANGELOG.md) for release information. - [JSON-Unit](#json-unit) - [Given](#given-1) - [Set path base directory for request/result/database files](#set-path-base-directory-for-requestresultdatabase-files) + - [Set a static value to the context](#set-a-static-value-to-the-context) + - [Set multiple static values to the context](#set-multiple-static-values-to-the-context) - [Set base path for URLs](#set-base-path-for-urls) - [Define that a token without scopes should be used.](#define-that-a-token-without-scopes-should-be-used) - [Set a URI path for later execution](#set-a-uri-path-for-later-execution) @@ -128,6 +130,8 @@ The conversion of the database result to CSV is done internally. ## REST The REST API steps can be prepared with some given steps. +If a path contains a template placeholder with `${}` like `${elementFromContext}` the library tries to replace this with `elementFromContext` in the context, if it exists. + ### JSON-Unit The library contains already two matchers: @@ -160,6 +164,42 @@ It is used for: - Database query files (`.sql`) - Database CSV result files (`.csv`) +#### Set a static value to the context +```gherkin +Scenario: Test with static key/value added to the context + Given that the context contains the key "staticKey" with the value "staticValue" + And that the API path is "/api/v1/${staticKey}/myApi" + When executing a GET call with previously given URI +``` + +or use it in an outline scenario: + +```gherkin +Scenario Outline: Test with static key/value added to the context in an outline scenario + Given that the context contains the key "" with the value "" + And that the API path is "/api/v1/${staticURLElement}" + When executing a GET call with previously given URI + Examples: + | staticKey | staticValue | + | staticURLElement | resourceA | + | staticURLElement | resourceB | + | staticURLElement | resourceC | +``` + +It adds the key/value pairs to the context. Please ensure, that those static values are unique to avoid overwriting them in later steps. + +#### Set multiple static values to the context +```gherkin +Scenario: Test with static key/value added to the context via table + Given that the context contains the following 'key' and 'value' pairs + | staticFirstElement | resourceA | + | staticSecondElement | resourceB | + And that the API path is "/api/v1/${staticFirstElement}/${staticSecondElement}" + When executing a GET call with previously given URI +``` + +It adds the key/value pairs to the context. Please ensure, that those static values are unique to avoid overwriting them in later steps. + #### Set base path for URLs ```gherkin Scenario: @@ -169,6 +209,8 @@ Scenario: Sets an internal `base URL path` for all URLs in the `Scenario` or `Feature`. This is very useful to avoid repeating e.g. `/api/v1/myapitotest` before every concrete endpoint. +It is also possible to use placeholders with `${placeholder}` syntax. They will be replaced from the context. + #### Define that a token without scopes should be used. ```gherkin Scenario: diff --git a/build.gradle b/build.gradle index f1a83a9..7d44e82 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,8 @@ dependencies { api 'javax.validation:validation-api:2.0.1.Final' + api 'org.apache.commons:commons-text:1.9' + implementation 'org.liquibase:liquibase-core' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/gradle.properties b/gradle.properties index 2c538d3..9d79324 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.ragin.bdd -version=1.14.0 +version=1.15.0 systemProp.sonar.host.url=https://sonarcloud.io/ systemProp.sonar.organization=ragin-lundf-github diff --git a/src/main/java/com/ragin/bdd/cucumber/glue/BaseRESTExecutionGlue.java b/src/main/java/com/ragin/bdd/cucumber/glue/BaseRESTExecutionGlue.java index 272925a..b223f54 100644 --- a/src/main/java/com/ragin/bdd/cucumber/glue/BaseRESTExecutionGlue.java +++ b/src/main/java/com/ragin/bdd/cucumber/glue/BaseRESTExecutionGlue.java @@ -8,6 +8,7 @@ import javax.annotation.PostConstruct; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; @@ -77,6 +78,7 @@ protected void executeRequest( } else { path = ScenarioStateContext.current().getUriPath(); } + path = replacePathPlaceholders(path); // Prepare headers HttpHeaders headers = RESTCommunicationUtils.createHTTPHeader(authorized); @@ -99,13 +101,27 @@ protected void executeRequest( ); } + /** + * Replace placeholder like ${myContextItem} with the available items from the context + * + * @param path Path which can contain the placeholder + * @return replaced path + */ + protected String replacePathPlaceholders(final String path) { + // Build StringSubstitutor + StringSubstitutor sub = new StringSubstitutor(ScenarioStateContext.current().getScenarioContextMap()); + + // Replace + return sub.replace(path); + } + /** * Full URL for URI path * * @param path Path of URI * @return full URL as protocol:server_host:port/basePath/path */ - protected String fullURLFor(String path) { + protected String fullURLFor(final String path) { return SERVER_URL + port + ScenarioStateContext.current().getUrlBasePath() + path; } } diff --git a/src/main/java/com/ragin/bdd/cucumber/glue/GivenRESTStateGlue.java b/src/main/java/com/ragin/bdd/cucumber/glue/GivenRESTStateGlue.java index d105e01..dbd8245 100644 --- a/src/main/java/com/ragin/bdd/cucumber/glue/GivenRESTStateGlue.java +++ b/src/main/java/com/ragin/bdd/cucumber/glue/GivenRESTStateGlue.java @@ -2,8 +2,11 @@ import com.ragin.bdd.cucumber.core.BaseCucumberCore; import com.ragin.bdd.cucumber.core.ScenarioStateContext; +import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import java.io.IOException; +import java.util.Map; +import java.util.Set; import javax.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Value; @@ -73,4 +76,37 @@ public void givenThatTheFileIsUsedAsTheBody(@NotNull String pathToFile) throws I public void givenThatTheBodyOfRequestIs(@NotNull final String body) { ScenarioStateContext.current().setEditableBody(body); } + + /** + * Offers the possibility to set static values to the context + * + * @param key Key in the context map + * @param value Value that should be used + */ + @Given("that the context contains the key {string} with the value {string}") + public void givenThatContextContainsKeyValue(@NotNull final String key, @NotNull final String value) { + ScenarioStateContext.current().getScenarioContextMap().put(key, value); + } + + /** + * With this sentence it is possible to add static values to the context map. + * + *

+ * DataTable looks like: + *

+     * | key | value |
+     * | resourceId    | abc-def-gh |
+     * | subResourceId | zyx-wvu-ts |
+     * 
+ * + * @param dataTable DataTable with the key "key" and value "value" + */ + @Given("that the context contains the following 'key' and 'value' pairs") + public void givenThatContextContainsKeyValuePairFromDataTable(final DataTable dataTable) { + final Map contextDataTableMap = dataTable.asMap(String.class, String.class); + final Set keySet = contextDataTableMap.keySet(); + for (String key : keySet) { + ScenarioStateContext.current().getScenarioContextMap().put(key, contextDataTableMap.get(key)); + } + } } diff --git a/src/main/java/com/ragin/bdd/cucumber/glue/WhenRESTExecutionGETGlue.java b/src/main/java/com/ragin/bdd/cucumber/glue/WhenRESTExecutionGETGlue.java index 9fa7c65..39d8309 100644 --- a/src/main/java/com/ragin/bdd/cucumber/glue/WhenRESTExecutionGETGlue.java +++ b/src/main/java/com/ragin/bdd/cucumber/glue/WhenRESTExecutionGETGlue.java @@ -72,7 +72,7 @@ public void whenExecutingAnAuthorizedGETCallToUrl(final String path) { * @param dataTable DataTable which contains the mapping of dynamic elements and values */ @When("executing an authorized GET call with previously given API path and these dynamic 'URI Elements' replaced with the 'URI Values'") - public void whenExecutingAnAuthorizedGETCallToPathWithDynamicURLElement(DataTable dataTable) { + public void whenExecutingAnAuthorizedGETCallToPathWithDynamicURLElement(final DataTable dataTable) { executeRequest(dataTable, HttpMethod.GET, true); } @@ -99,7 +99,7 @@ public void whenExecutingAnAuthorizedGETCallToPathWithDynamicURLElement(DataTabl * @param dataTable DataTable which contains the mapping of dynamic elements and values */ @When("executing a GET call with previously given API path and the dynamic 'URI Elements' replaced with the 'URI Values'") - public void whenExecutingAGETCallToPathWithDynamicURLElement(DataTable dataTable) { + public void whenExecutingAGETCallToPathWithDynamicURLElement(final DataTable dataTable) { executeRequest(dataTable, HttpMethod.GET, false); } }