From 46546a990151019b8c429847fecde1ebdf47f9bb Mon Sep 17 00:00:00 2001 From: Sergii Date: Thu, 2 Aug 2018 17:11:09 -0700 Subject: [PATCH] #38 Cucumber-based rest api call testing framework --- ui/web/pom.xml | 25 +++++ .../test/ui/CucumberIntegrationTest.java | 14 +++ .../test/ui/CucumberLocalTest.java | 12 +++ .../test/ui/HeaderSettingRequestCallback.java | 33 ++++++ .../gallerymine/test/ui/ResponseResults.java | 29 +++++ .../test/ui/SpringIntegrationTest.java | 91 ++++++++++++++++ .../test/ui/StepDefsIntegrationTest.java | 102 ++++++++++++++++++ ui/web/src/test/resources/application.yml | 8 ++ ui/web/src/test/resources/folders.feature | 7 ++ 9 files changed, 321 insertions(+) create mode 100644 ui/web/src/test/java/gallerymine/test/ui/CucumberIntegrationTest.java create mode 100644 ui/web/src/test/java/gallerymine/test/ui/CucumberLocalTest.java create mode 100644 ui/web/src/test/java/gallerymine/test/ui/HeaderSettingRequestCallback.java create mode 100644 ui/web/src/test/java/gallerymine/test/ui/ResponseResults.java create mode 100644 ui/web/src/test/java/gallerymine/test/ui/SpringIntegrationTest.java create mode 100644 ui/web/src/test/java/gallerymine/test/ui/StepDefsIntegrationTest.java create mode 100644 ui/web/src/test/resources/application.yml create mode 100644 ui/web/src/test/resources/folders.feature diff --git a/ui/web/pom.xml b/ui/web/pom.xml index 3714ae5..57d2b8b 100644 --- a/ui/web/pom.xml +++ b/ui/web/pom.xml @@ -25,6 +25,7 @@ UTF-8 ${project.build.sourceEncoding} 3.6 + 1.2.5 1.8 @@ -238,6 +239,30 @@ batik-svggen + + info.cukes + cucumber-core + ${cucumber.java.version} + test + + + info.cukes + cucumber-java + ${cucumber.java.version} + test + + + info.cukes + cucumber-junit + ${cucumber.java.version} + test + + + info.cukes + cucumber-spring + ${cucumber.java.version} + test + diff --git a/ui/web/src/test/java/gallerymine/test/ui/CucumberIntegrationTest.java b/ui/web/src/test/java/gallerymine/test/ui/CucumberIntegrationTest.java new file mode 100644 index 0000000..34cf84c --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/CucumberIntegrationTest.java @@ -0,0 +1,14 @@ +package gallerymine.test.ui; + +import org.junit.runner.RunWith; + +import cucumber.api.CucumberOptions; +import cucumber.api.junit.Cucumber; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +@RunWith(Cucumber.class) +@CucumberOptions(features = "src/test/resources") +public class CucumberIntegrationTest extends SpringIntegrationTest { + +} \ No newline at end of file diff --git a/ui/web/src/test/java/gallerymine/test/ui/CucumberLocalTest.java b/ui/web/src/test/java/gallerymine/test/ui/CucumberLocalTest.java new file mode 100644 index 0000000..06b2687 --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/CucumberLocalTest.java @@ -0,0 +1,12 @@ +package gallerymine.test.ui; + +import cucumber.api.CucumberOptions; +import cucumber.api.java.Before; +import cucumber.api.junit.Cucumber; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(features = "src/test/resources") +public class CucumberLocalTest extends SpringIntegrationTest { + +} \ No newline at end of file diff --git a/ui/web/src/test/java/gallerymine/test/ui/HeaderSettingRequestCallback.java b/ui/web/src/test/java/gallerymine/test/ui/HeaderSettingRequestCallback.java new file mode 100644 index 0000000..37a9f8d --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/HeaderSettingRequestCallback.java @@ -0,0 +1,33 @@ +package gallerymine.test.ui; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.web.client.RequestCallback; + +import java.io.IOException; +import java.util.Map; + +public class HeaderSettingRequestCallback implements RequestCallback { + final Map requestHeaders; + + private String body; + + public HeaderSettingRequestCallback(final Map headers) { + this.requestHeaders = headers; + } + + public void setBody(final String postBody) { + this.body = postBody; + } + + @Override + public void doWithRequest(ClientHttpRequest request) throws IOException { + final HttpHeaders clientHeaders = request.getHeaders(); + for (final Map.Entry entry : requestHeaders.entrySet()) { + clientHeaders.add(entry.getKey(), entry.getValue()); + } + if (null != body) { + request.getBody().write(body.getBytes()); + } + } +} \ No newline at end of file diff --git a/ui/web/src/test/java/gallerymine/test/ui/ResponseResults.java b/ui/web/src/test/java/gallerymine/test/ui/ResponseResults.java new file mode 100644 index 0000000..a706b2a --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/ResponseResults.java @@ -0,0 +1,29 @@ +package gallerymine.test.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; + +import org.apache.commons.io.IOUtils; +import org.springframework.http.client.ClientHttpResponse; + +public class ResponseResults { + private final ClientHttpResponse theResponse; + private final String body; + + ResponseResults(final ClientHttpResponse response) throws IOException { + this.theResponse = response; + final InputStream bodyInputStream = response.getBody(); + final StringWriter stringWriter = new StringWriter(); + IOUtils.copy(bodyInputStream, stringWriter); + this.body = stringWriter.toString(); + } + + ClientHttpResponse getTheResponse() { + return theResponse; + } + + String getBody() { + return body; + } +} \ No newline at end of file diff --git a/ui/web/src/test/java/gallerymine/test/ui/SpringIntegrationTest.java b/ui/web/src/test/java/gallerymine/test/ui/SpringIntegrationTest.java new file mode 100644 index 0000000..07cc749 --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/SpringIntegrationTest.java @@ -0,0 +1,91 @@ +package gallerymine.test.ui; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import cucumber.api.java.Before; +import gallerymine.GalleryMineApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +//@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = GalleryMineApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) +@ContextConfiguration +public class SpringIntegrationTest { + static ResponseResults latestResponse = null; + static ResponseEntity latestResponseMap = null; + + @LocalServerPort + public int serverPort; + + @Autowired + protected RestTemplate restTemplate; + + void executeGet(String url) throws IOException { + final Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + final HeaderSettingRequestCallback requestCallback = new HeaderSettingRequestCallback(headers); + final ResponseResultErrorHandler errorHandler = new ResponseResultErrorHandler(); + + restTemplate.setErrorHandler(errorHandler); + latestResponse = restTemplate.execute(url, HttpMethod.GET, requestCallback, response -> { + if (errorHandler.hadError) { + return (errorHandler.getResults()); + } else { + return (new ResponseResults(response)); + } + }); + } + + void executePost() throws IOException { + final Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + final HeaderSettingRequestCallback requestCallback = new HeaderSettingRequestCallback(headers); + final ResponseResultErrorHandler errorHandler = new ResponseResultErrorHandler(); + + if (restTemplate == null) { + restTemplate = new RestTemplate(); + } + + restTemplate.setErrorHandler(errorHandler); + latestResponse = restTemplate + .execute("http://localhost:"+serverPort+"/baeldung", HttpMethod.POST, requestCallback, response -> { + if (errorHandler.hadError) { + return (errorHandler.getResults()); + } else { + return (new ResponseResults(response)); + } + }); + } + + private class ResponseResultErrorHandler implements ResponseErrorHandler { + private ResponseResults results = null; + private Boolean hadError = false; + + private ResponseResults getResults() { + return results; + } + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + hadError = response.getRawStatusCode() >= 400; + return hadError; + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + results = new ResponseResults(response); + } + } +} \ No newline at end of file diff --git a/ui/web/src/test/java/gallerymine/test/ui/StepDefsIntegrationTest.java b/ui/web/src/test/java/gallerymine/test/ui/StepDefsIntegrationTest.java new file mode 100644 index 0000000..3ce1608 --- /dev/null +++ b/ui/web/src/test/java/gallerymine/test/ui/StepDefsIntegrationTest.java @@ -0,0 +1,102 @@ +package gallerymine.test.ui; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import cucumber.api.java.en.Given; +import gallerymine.model.mvc.SourceCriteria; +import gallerymine.model.support.PictureGrade; +import org.springframework.context.expression.MapAccessor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.ReflectivePropertyAccessor; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; + +import cucumber.api.java.en.And; +import cucumber.api.java.en.Then; +import cucumber.api.java.en.When; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.HashMap; + +public class StepDefsIntegrationTest extends SpringIntegrationTest { + + @When("^the client calls /baeldung$") + public void the_client_issues_POST_hello() throws Throwable { + executePost(); + } + + @Given("^the client calls /hello$") + public void the_client_issues_GET_hello() throws Throwable { + executeGet("http://localhost:"+serverPort+"/hello"); + } + + @Given("^api call /listFolders") + public void the_client_issues_list_folders() { + SourceCriteria searchCriteria = new SourceCriteria(); + searchCriteria.setPage(0); + searchCriteria.setSize(10); + searchCriteria.setPath("test"); + searchCriteria.setGrade(PictureGrade.GALLERY); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Accept", "application/json"); + headers.add("Content-Type", "application/json; charset=UTF-8"); + HttpEntity request = new HttpEntity<>(searchCriteria, headers); + latestResponseMap = restTemplate .exchange("http://localhost:"+serverPort+"/sources/findPath", HttpMethod.POST, request, HashMap.class); + System.out.print("Run"); + } + + @When("^the client calls /version$") + public void the_client_issues_GET_version() throws Throwable { + executeGet("http://localhost:"+serverPort+"/version"); + } + + @Then("^response status code is (\\d+)$") + public void the_client_receives_status_code_of(int statusCode) { + final HttpStatus currentStatusCode = latestResponseMap.getStatusCode(); + assertThat("status code is incorrect : " + latestResponseMap.getStatusCode(), currentStatusCode.value(), is(statusCode)); + } + + @And("^the client receives server version (.+)$") + public void the_client_receives_server_version_body(String version) { + assertThat(latestResponse.getBody(), is(version)); + } + + @And("^response has (.+)$") + public void responseHas(String key) { + assertTrue("Key not found in response key='"+key+"'", latestResponseMap.getBody().containsKey(key)); + } + + @And("^response key (.+) equal to (.+)$") + public void responseValue(String key, String valueStr) { + Object valueExpected = resolveExpression(valueStr); + Object valueActual = valueExpected == null ? resolveExpression(key) : resolveExpression(key, valueExpected.getClass()); + assertEquals("Value not equal for '"+key+"' which is '"+valueActual+"' != '"+valueExpected+"'",valueExpected, valueActual); + } + + public Object resolveExpression(String expression) { + return resolveExpression(expression , null); + } + + public Object resolveExpression(String expression, Class clazzRequired) { + SpelParserConfiguration config = new SpelParserConfiguration(true,true); + ExpressionParser parser = new SpelExpressionParser(config); + StandardEvaluationContext context = new StandardEvaluationContext(latestResponseMap); + context.addPropertyAccessor(new MapAccessor()); + Expression exp = parser.parseExpression(expression); + if (clazzRequired != null) { + return exp.getValue(context, clazzRequired); + } else { + return exp.getValue(context); + } + } +} \ No newline at end of file diff --git a/ui/web/src/test/resources/application.yml b/ui/web/src/test/resources/application.yml new file mode 100644 index 0000000..92b2b40 --- /dev/null +++ b/ui/web/src/test/resources/application.yml @@ -0,0 +1,8 @@ +spring: + data: + mongodb.uri: mongodb://localhost:27017/galleryMineTest + +javamelody: + # Enable JavaMelody auto-configuration (optional, default: true) + enabled: false + diff --git a/ui/web/src/test/resources/folders.feature b/ui/web/src/test/resources/folders.feature new file mode 100644 index 0000000..58fb84e --- /dev/null +++ b/ui/web/src/test/resources/folders.feature @@ -0,0 +1,7 @@ +Feature: Test Main Controller methods + + Scenario: Test list Folders of gallery + When api call /listFolders + Then response status code is 200 + And response key body.status equal to 200 + And response key body.list.size equal to 10