From b42774e1ab8cb868c3ce8dfb90cbf79a5e4e7818 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 24 Apr 2016 21:08:31 +0200 Subject: [PATCH] Converted docs to adoc --- .../DslToWireMockClientConverterSpec.groovy | 66 + .../builder/ContractHttpDocsSpec.groovy | 299 ++++ .../JaxRsClientMethodBuilderSpec.groovy | 70 + .../MockMvcMethodBodyBuilderSpec.groovy | 1529 +++++++++-------- .../ofg/twitter/places/BaseMockMvcSpec.groovy | 2 + docs/src/docs/asciidoc/contract.adoc | 356 +--- docs/src/docs/asciidoc/images/Deps.png | Bin 0 -> 37192 bytes docs/src/docs/asciidoc/images/Stubs1.png | Bin 0 -> 35170 bytes docs/src/docs/asciidoc/images/Stubs2.png | Bin 0 -> 18143 bytes docs/src/docs/asciidoc/index.adoc | 28 +- docs/src/docs/asciidoc/introduction.adoc | 130 +- docs/src/docs/asciidoc/migration.adoc | 23 + 12 files changed, 1359 insertions(+), 1144 deletions(-) create mode 100644 accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy create mode 100644 docs/src/docs/asciidoc/images/Deps.png create mode 100644 docs/src/docs/asciidoc/images/Stubs1.png create mode 100644 docs/src/docs/asciidoc/images/Stubs2.png create mode 100644 docs/src/docs/asciidoc/migration.adoc diff --git a/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/DslToWireMockClientConverterSpec.groovy b/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/DslToWireMockClientConverterSpec.groovy index 24d0eb2845..2382878856 100755 --- a/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/DslToWireMockClientConverterSpec.groovy +++ b/accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/DslToWireMockClientConverterSpec.groovy @@ -171,4 +171,70 @@ class DslToWireMockClientConverterSpec extends Specification { } ''', json, false) } + + def 'should convert dsl to wiremock to show it in the docs'() { + given: + def converter = new DslToWireMockClientConverter() + and: + File file = tmpFolder.newFile("dsl_from_docs.groovy") + file.write(''' + io.codearte.accurest.dsl.GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + email: $(stub(optional(regex(email()))), test('abc@abc.com')), + callback_url: $(stub(regex(hostname())), test('http://partners.com')) + ) + } + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + code: value(stub("123123"), test(optional("123123"))), + message: "User not found by email == [${value(test(regex(email())), stub('not.existing@user.com'))}]" + ) + } + } + ''') + when: + String json = converter.convertContent("Test", new Contract(file.toPath(), false, 0, null)) + then: + JSONAssert.assertEquals( // tag::wiremock[] +''' +{ + "request" : { + "url" : "/users/password", + "method" : "POST", + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]" + }, { + "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" + } ], + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + } + }, + "response" : { + "status" : 404, + "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}", + "headers" : { + "Content-Type" : "application/json" + } + }, + "priority" : 1 +} +''' +// end::wiremock[] + , json, false) + } + } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy new file mode 100644 index 0000000000..235e8184b5 --- /dev/null +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy @@ -0,0 +1,299 @@ +package io.codearte.accurest.builder + +import io.codearte.accurest.dsl.GroovyDsl +import spock.lang.Specification +/** + * Tests used for the documentation + * + * @author Marcin Grzejszczak + */ +class ContractHttpDocsSpec extends Specification { + + GroovyDsl httpDsl = + // tag::http_dsl[] + io.codearte.accurest.dsl.GroovyDsl.make { + // Definition of HTTP request part of the contract + // (this can be a valid request or invalid depending + // on type of contract being specified). + request { + //... + } + + // Definition of HTTP response part of the contract + // (a service implementing this contract should respond + // with following response after receiving request + // specified in "request" part above). + response { + //... + } + + // Contract priority, which can be used for overriding + // contracts (1 is highest). Priority is optional. + priority 1 + } + // end::http_dsl[] + + GroovyDsl request = + // tag::request[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + // HTTP request method (GET/POST/PUT/DELETE). + method 'GET' + + // Path component of request URL is specified as follows. + urlPath('/users') + } + + response { + //... + } + } + // end::request[] + + GroovyDsl url = + // tag::url[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + method 'GET' + + // Specifying `url` and `urlPath` in one contract is illegal. + url('http://localhost:8888/users') + } + + response { + //... + } + } + // end::url[] + + GroovyDsl urlPaths = + // tag::urlpath[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + //... + + urlPath('/users') { + + // Each parameter is specified in form + // `'paramName' : paramValue` where parameter value + // may be a simple literal or one of matcher functions, + // all of which are used in this example. + queryParameters { + + // If a simple literal is used as value + // default matcher function is used (equalTo) + parameter 'limit': 100 + + // `equalTo` function simply compares passed value + // using identity operator (==). + parameter 'filter': equalTo("email") + + // `containing` function matches strings + // that contains passed substring. + parameter 'gender': value(stub(containing("[mf]")), server('mf')) + + // `matching` function tests parameter + // against passed regular expression. + parameter 'offset': value(stub(matching("[0-9]+")), server(123)) + + // `notMatching` functions tests if parameter + // does not match passed regular expression. + parameter 'loginStartsWith': value(stub(notMatching(".{0,2}")), server(3)) + } + } + + //... + } + + response { + //... + } + } + // end::urlpath[] + + GroovyDsl headers = + // tag::headers[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + //... + + // Each header is added in form `'Header-Name' : 'Header-Value'`. + headers { + header 'Content-Type': 'application/json' + } + + //... + } + + response { + //... + } + } + // end::headers[] + + GroovyDsl body = + // tag::body[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + //... + + // JSON and XML formats of request body are supported. + // Format will be determined from a header or body's content. + body '''{ "login" : "john", "name": "John The Contract" }''' + } + + response { + //... + } + } + // end::body[] + + GroovyDsl bodyAsXml = + // tag::bodyAsXml[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + //... + + // In this case body will be formatted as XML. + body equalToXml( + '''johnJohn The Contract''' + ) + } + + response { + //... + } + } + // end::bodyAsXml[] + + GroovyDsl response = + // tag::response[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + //... + } + response { + // Status code sent by the server + // in response to request specified above. + status 200 + } + } + // end::response[] + + GroovyDsl regex = + // tag::regex[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + method('GET') + url $(client(~/\/[0-9]{2}/), server('/12')) + } + response { + status 200 + body( + id: value( + client('123'), + server(regex('[0-9]+')) + ), + surname: $( + client('Kowalsky'), + server('Lewandowski') + ), + name: 'Jan', + created: $(client('2014-02-02 12:23:43'), server(execute('currentDate(it)'))), + correlationId: value(client('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'), + server(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')) + ) + ) + headers { + header 'Content-Type': 'text/plain' + } + } + } + // end::regex[] + + GroovyDsl optionals = + // tag::optionals[] + io.codearte.accurest.dsl.GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' + } + body( + email: $(stub(optional(regex(email()))), test('abc@abc.com')), + callback_url: $(stub(regex(hostname())), test('http://partners.com')) + ) + } + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + code: value(stub("123123"), test(optional("123123"))), + message: "User not found by email == [${value(test(regex(email())), stub('not.existing@user.com'))}]" + ) + } + } + // end::optionals[] + + def 'should convert dsl with optionals to proper Spock test'() { + given: + BlockBuilder blockBuilder = new BlockBuilder(" ") + new MockMvcSpockMethodRequestProcessingBodyBuilder(optionals).appendTo(blockBuilder) + expect: + stripped(blockBuilder.toString()) == stripped( +// tag::optionals_test[] +""" +given: + def request = given() + .header('Content-Type', 'application/json') + .body('''{"email":"abc@abc.com","callback_url":"http://partners.com"}''') + +when: + def response = given().spec(request) + .post("/users/password") + +then: + response.statusCode == 404 + response.header('Content-Type') == 'application/json' +and: + DocumentContext parsedJson = JsonPath.parse(response.body.asString()) + assertThatJson(parsedJson).field("message").matches("User not found by email == \\\\\\\\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,4}\\\\\\\\]") + assertThatJson(parsedJson).field("code").matches("(123123)?") +""" +// end::optionals_test[] +) + } + + GroovyDsl method = + // tag::method[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + method 'PUT' + url $(client(regex('^/api/[0-9]{2}$')), server('/api/12')) + headers { + header 'Content-Type': 'application/json' + } + body '''\ + [{ + "text": "Gonna see you at Warsaw" + }] + ''' + } + response { + body ( + path: $(client('/api/12'), server(regex('^/api/[0-9]{2}$'))), + correlationId: $(client('1223456'), server(execute('isProperCorrelationId($it)'))) + ) + status 200 + } + } + // end::method[] + + private String stripped(String string) { + return string.stripMargin().stripIndent().replace('\t', '').replace('\n', '') + } +} diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy index 0485a38d4d..3d36d7676f 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy @@ -591,4 +591,74 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub "JaxRsClientJUnitMethodBodyBuilder" | { GroovyDsl dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl) } | 'method("GET")' } + def "should generate a call with an url path and query parameters with JUnit - we'll put it into docs"() { + given: + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'GET' + urlPath('/users') { + queryParameters { + parameter 'limit': $(client(equalTo("20")), server(equalTo("10"))) + parameter 'offset': $(client(containing("20")), server(equalTo("20"))) + parameter 'filter': "email" + parameter 'sort': equalTo("name") + parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55")) + parameter 'age': $(client(notMatching("^\\w*\$")), server("99")) + parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov")) + parameter 'email': "bob@email.com" + parameter 'hello': $(client(matching("Denis.*")), server(absent())) + parameter 'hello': absent() + } + } + } + response { + status 200 + body """ + { + "property1": "a", + "property2": "b" + } + """ + } + } + MethodBodyBuilder builder = new JaxRsClientJUnitMethodBodyBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() + then: + stripped(test) == stripped( // tag::jaxrs[] + ''' + // when: + Response response = webTarget + .path("/users") + .queryParam("limit", "10") + .queryParam("offset", "20") + .queryParam("filter", "email") + .queryParam("sort", "name") + .queryParam("search", "55") + .queryParam("age", "99") + .queryParam("name", "Denis.Stepanov") + .queryParam("email", "bob@email.com") + .request() + .method("GET"); + + String responseAsString = response.readEntity(String.class); + + // then: + assertThat(response.getStatus()).isEqualTo(200); + // and: + DocumentContext parsedJson = JsonPath.parse(responseAsString); + assertThatJson(parsedJson).field("property1").isEqualTo("a"); + assertThatJson(parsedJson).field("property2").isEqualTo("b"); +''' +// end::jaxrs[] +) + and: + stubMappingIsValidWireMockStub(contractDsl) + } + + private String stripped(String string) { + return string.stripMargin().stripIndent().replace('\t', '').replace('\n', '') + } } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy index 03d70d99ba..eaa303fb52 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy @@ -49,8 +49,12 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub } body( """ { - "email" : "${value(stub(optional(regex(email()))), test('abc@abc.com'))}", - "callback_url" : "${value(client(regex(hostname())), server('http://partners.com'))}" + "email" : "${ + value(stub(optional(regex(email()))), test('abc@abc.com')) + }", + "callback_url" : "${ + value(client(regex(hostname())), server('http://partners.com')) + }" } """ ) @@ -74,1087 +78,1138 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub def "should generate assertions for simple response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body """{ + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body """{ "property1": "a", "property2": "b" }""" - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").isEqualTo("b")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").isEqualTo("b")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue("#187") def "should generate assertions for null and boolean values with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body """{ + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body """{ "property1": "true", "property2": null, "property3": false }""" - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("true")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").isNull()""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property3").isEqualTo(false)""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("true")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").isNull()""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property3").isEqualTo(false)""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue("#79") def "should generate assertions for simple response body constructed from map with a list with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body( - property1: 'a', - property2: [ - [a: 'sth'], - [b: 'sthElse'] - ] - ) - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + body( + property1: 'a', + property2: [ + [a: 'sth'], + [b: 'sthElse'] + ] + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("a").isEqualTo("sth")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("b").isEqualTo("sthElse")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("a").isEqualTo("sth")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("b").isEqualTo("sthElse")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue("#82") def "should generate proper request when body constructed from map with a list #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - body( - items: ['HOP'] - ) - } - response { - status 200 - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + body( + items: ['HOP'] + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains(bodyString) + blockBuilder.toString().contains(bodyString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | bodyString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """.body('''{\"items\":[\"HOP\"]}''')""" - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '.body("{\\"items\\":[\\"HOP\\"]}")' + methodBuilderName | methodBuilder | bodyString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """.body('''{\"items\":[\"HOP\"]}''')""" + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '.body("{\\"items\\":[\\"HOP\\"]}")' } @Issue("#88") def "should generate proper request when body constructed from GString with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - body( - "property1=VAL1" - ) - } - response { - status 200 - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + body( + "property1=VAL1" + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains(bodyString) + blockBuilder.toString().contains(bodyString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | bodyString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """.body('''property1=VAL1''')""" - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '.body("\\"property1=VAL1\\"")' + methodBuilderName | methodBuilder | bodyString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """.body('''property1=VAL1''')""" + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '.body("\\"property1=VAL1\\"")' } @Issue("185") def "should generate assertions for a response body containing map with integers as keys with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body( - property: [ - 14: 0.0, - 7 : 0.0 - ] - ) - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + body( + property: [ + 14: 0.0, + 7 : 0.0 + ] + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").field(7).isEqualTo(0.0)""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").field(14).isEqualTo(0.0)""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").field(7).isEqualTo(0.0)""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").field(14).isEqualTo(0.0)""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate assertions for array in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body """[ + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body """[ { "property1": "a" }, { "property2": "b" }]""" - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).array().contains("property2").isEqualTo("b")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).array().contains("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array().contains("property2").isEqualTo("b")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array().contains("property1").isEqualTo("a")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate assertions for array inside response body element with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body """{ + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body """{ "property1": [ { "property2": "test1"}, { "property3": "test2"} ] }""" - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property1").contains("property2").isEqualTo("test1")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property1").contains("property3").isEqualTo("test2")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property1").contains("property2").isEqualTo("test1")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property1").contains("property3").isEqualTo("test2")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate assertions for nested objects in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body '''\ + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body '''\ { "property1": "a", "property2": {"property3": "b"} } ''' - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").field("property3").isEqualTo("b")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").field("property3").isEqualTo("b")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate regex assertions for map objects in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body( - property1: "a", - property2: value( - client('123'), - server(regex('[0-9]{3}')) - ) - ) - headers { - header('Content-Type': 'application/json') - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body( + property1: "a", + property2: value( + client('123'), + server(regex('[0-9]{3}')) + ) + ) + headers { + header('Content-Type': 'application/json') } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").matches("[0-9]{3}")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").matches("[0-9]{3}")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate regex assertions for string objects in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body("""{"property1":"a","property2":"${value(client('123'), server(regex('[0-9]{3}')))}"}""") - headers { - header('Content-Type': 'application/json') - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body("""{"property1":"a","property2":"${ + value(client('123'), server(regex('[0-9]{3}'))) + }"}""") + headers { + header('Content-Type': 'application/json') } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").matches("[0-9]{3}")""") - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property2").matches("[0-9]{3}")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue(["#126", "#143"]) def "should generate escaped regex assertions for string objects in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - url "test" - } - response { - status 200 - body("""{"property":" ${value(client('123'), server(regex('\\d+')))}"}""") - headers { - header('Content-Type': 'application/json') - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + url "test" + } + response { + status 200 + body("""{"property":" ${ + value(client('123'), server(regex('\\d+'))) + }"}""") + headers { + header('Content-Type': 'application/json') } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) + builder.appendTo(blockBuilder) then: - blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").matches("\\\\d+")""") + blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property").matches("\\\\d+")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate a call with an url path and query parameters with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'GET' - urlPath('/users') { - queryParameters { - parameter 'limit': $(client(equalTo("20")), server(equalTo("10"))) - parameter 'offset': $(client(containing("20")), server(equalTo("20"))) - parameter 'filter': "email" - parameter 'sort': equalTo("name") - parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55")) - parameter 'age': $(client(notMatching("^\\w*\$")), server("99")) - parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov")) - parameter 'email': "bob@email.com" - parameter 'hello': $(client(matching("Denis.*")), server(absent())) - parameter 'hello': absent() - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'GET' + urlPath('/users') { + queryParameters { + parameter 'limit': $(client(equalTo("20")), server(equalTo("10"))) + parameter 'offset': $(client(containing("20")), server(equalTo("20"))) + parameter 'filter': "email" + parameter 'sort': equalTo("name") + parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55")) + parameter 'age': $(client(notMatching("^\\w*\$")), server("99")) + parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov")) + parameter 'email': "bob@email.com" + parameter 'hello': $(client(matching("Denis.*")), server(absent())) + parameter 'hello': absent() } } - response { - status 200 - body """ + } + response { + status 200 + body """ { "property1": "a", "property2": "b" } """ - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('get("/users?limit=10&offset=20&filter=email&sort=name&search=55&age=99&name=Denis.Stepanov&email=bob@email.com")') - test.contains('assertThatJson(parsedJson).field("property1").isEqualTo("a")') - test.contains('assertThatJson(parsedJson).field("property2").isEqualTo("b")') + test.contains('get("/users?limit=10&offset=20&filter=email&sort=name&search=55&age=99&name=Denis.Stepanov&email=bob@email.com")') + test.contains('assertThatJson(parsedJson).field("property1").isEqualTo("a")') + test.contains('assertThatJson(parsedJson).field("property2").isEqualTo("b")') and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue('#169') def "should generate a call with an url path and query parameters with url containing a pattern with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'GET' - url($(stub(regex('/foo/[0-9]+')), test('/foo/123456'))) { - queryParameters { - parameter 'limit': $(client(equalTo("20")), server(equalTo("10"))) - parameter 'offset': $(client(containing("20")), server(equalTo("20"))) - parameter 'filter': "email" - parameter 'sort': equalTo("name") - parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55")) - parameter 'age': $(client(notMatching("^\\w*\$")), server("99")) - parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov")) - parameter 'email': "bob@email.com" - parameter 'hello': $(client(matching("Denis.*")), server(absent())) - parameter 'hello': absent() - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'GET' + url($(stub(regex('/foo/[0-9]+')), test('/foo/123456'))) { + queryParameters { + parameter 'limit': $(client(equalTo("20")), server(equalTo("10"))) + parameter 'offset': $(client(containing("20")), server(equalTo("20"))) + parameter 'filter': "email" + parameter 'sort': equalTo("name") + parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55")) + parameter 'age': $(client(notMatching("^\\w*\$")), server("99")) + parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov")) + parameter 'email': "bob@email.com" + parameter 'hello': $(client(matching("Denis.*")), server(absent())) + parameter 'hello': absent() } } - response { - status 200 - body """ + } + response { + status 200 + body """ { "property1": "a", "property2": "b" } """ - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('get("/foo/123456?limit=10&offset=20&filter=email&sort=name&search=55&age=99&name=Denis.Stepanov&email=bob@email.com")') - test.contains('assertThatJson(parsedJson).field("property1").isEqualTo("a")') - test.contains('assertThatJson(parsedJson).field("property2").isEqualTo("b")') + test.contains('get("/foo/123456?limit=10&offset=20&filter=email&sort=name&search=55&age=99&name=Denis.Stepanov&email=bob@email.com")') + test.contains('assertThatJson(parsedJson).field("property1").isEqualTo("a")') + test.contains('assertThatJson(parsedJson).field("property2").isEqualTo("b")') and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should generate test for empty body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method('POST') - url("/ws/payments") - body("") - } - response { - status 406 - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method('POST') + url("/ws/payments") + body("") } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 406 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains(bodyString) + test.contains(bodyString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | bodyString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ".body('''''')" - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ".body(\"\\\"\\\"\")" + methodBuilderName | methodBuilder | bodyString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ".body('''''')" + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ".body(\"\\\"\\\"\")" } def "should generate test for String in response body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "POST" - url "test" - } - response { - status 200 - body "test" - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "POST" + url "test" } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + body "test" + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains(bodyDefinitionString) - test.contains(bodyEvaluationString) + test.contains(bodyDefinitionString) + test.contains(bodyEvaluationString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | bodyDefinitionString | bodyEvaluationString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | 'def responseBody = (response.body.asString())' | 'responseBody == "test"' - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'Object responseBody = (response.getBody().asString());' | 'assertThat(responseBody).isEqualTo("test");' + methodBuilderName | methodBuilder | bodyDefinitionString | bodyEvaluationString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | 'def responseBody = (response.body.asString())' | 'responseBody == "test"' + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'Object responseBody = (response.getBody().asString());' | 'assertThat(responseBody).isEqualTo("test");' } @Issue('113') def "should generate regex test for String in response header with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'POST' - url $(client(regex('/partners/[0-9]+/users')), server('/partners/1000/users')) - headers { header 'Content-Type': 'application/json' } - body( - first_name: 'John', - last_name: 'Smith', - personal_id: '12345678901', - phone_number: '500500500', - invitation_token: '00fec7141bb94793bfe7ae1d0f39bda0', - password: 'john' - ) - } - response { - status 201 - headers { - header 'Location': $(client('http://localhost/partners/1000/users/1001'), server(regex('http://localhost/partners/[0-9]+/users/[0-9]+'))) - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'POST' + url $(client(regex('/partners/[0-9]+/users')), server('/partners/1000/users')) + headers { header 'Content-Type': 'application/json' } + body( + first_name: 'John', + last_name: 'Smith', + personal_id: '12345678901', + phone_number: '500500500', + invitation_token: '00fec7141bb94793bfe7ae1d0f39bda0', + password: 'john' + ) + } + response { + status 201 + headers { + header 'Location': $(client('http://localhost/partners/1000/users/1001'), server(regex('http://localhost/partners/[0-9]+/users/[0-9]+'))) } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains(headerEvaluationString) + test.contains(headerEvaluationString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | headerEvaluationString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '''response.header('Location') ==~ java.util.regex.Pattern.compile('http://localhost/partners/[0-9]+/users/[0-9]+')''' - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'assertThat(response.header("Location")).matches("http://localhost/partners/[0-9]+/users/[0-9]+");' + methodBuilderName | methodBuilder | headerEvaluationString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '''response.header('Location') ==~ java.util.regex.Pattern.compile('http://localhost/partners/[0-9]+/users/[0-9]+')''' + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'assertThat(response.header("Location")).matches("http://localhost/partners/[0-9]+/users/[0-9]+");' } @Issue('115') def "should generate regex with helper method with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'POST' - url $(client(regex('/partners/[0-9]+/users')), server('/partners/1000/users')) - headers { header 'Content-Type': 'application/json' } - body( - first_name: 'John', - last_name: 'Smith', - personal_id: '12345678901', - phone_number: '500500500', - invitation_token: '00fec7141bb94793bfe7ae1d0f39bda0', - password: 'john' - ) - } - response { - status 201 - headers { - header 'Location': $(client('http://localhost/partners/1000/users/1001'), server(regex("^${hostname()}/partners/[0-9]+/users/[0-9]+"))) - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'POST' + url $(client(regex('/partners/[0-9]+/users')), server('/partners/1000/users')) + headers { header 'Content-Type': 'application/json' } + body( + first_name: 'John', + last_name: 'Smith', + personal_id: '12345678901', + phone_number: '500500500', + invitation_token: '00fec7141bb94793bfe7ae1d0f39bda0', + password: 'john' + ) + } + response { + status 201 + headers { + header 'Location': $(client('http://localhost/partners/1000/users/1001'), server(regex("^${hostname()}/partners/[0-9]+/users/[0-9]+"))) } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains(headerEvaluationString) + test.contains(headerEvaluationString) and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder | headerEvaluationString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '''response.header('Location') ==~ java.util.regex.Pattern.compile('^((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?/partners/[0-9]+/users/[0-9]+')''' - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'assertThat(response.header("Location")).matches("^((http[s]?|ftp):/)/?([^:/s]+)(:[0-9]{1,5})?/partners/[0-9]+/users/[0-9]+");' + methodBuilderName | methodBuilder | headerEvaluationString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '''response.header('Location') ==~ java.util.regex.Pattern.compile('^((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?/partners/[0-9]+/users/[0-9]+')''' + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | 'assertThat(response.header("Location")).matches("^((http[s]?|ftp):/)/?([^:/s]+)(:[0-9]{1,5})?/partners/[0-9]+/users/[0-9]+");' } def "should work with more complex stuff and jsonpaths with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - priority 10 - request { - method 'POST' - url '/validation/client' - headers { - header 'Content-Type': 'application/json' - } - body( - bank_account_number: '0014282912345698765432161182', - email: 'foo@bar.com', - phone_number: '100299300', - personal_id: 'ABC123456' - ) - } - - response { - status 200 - body(errors: [ - [property: "bank_account_number", message: "incorrect_format"] - ]) + GroovyDsl contractDsl = GroovyDsl.make { + priority 10 + request { + method 'POST' + url '/validation/client' + headers { + header 'Content-Type': 'application/json' } + body( + bank_account_number: '0014282912345698765432161182', + email: 'foo@bar.com', + phone_number: '100299300', + personal_id: 'ABC123456' + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + + response { + status 200 + body(errors: [ + [property: "bank_account_number", message: "incorrect_format"] + ]) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains("""assertThatJson(parsedJson).array("errors").contains("property").isEqualTo("bank_account_number")""") - test.contains("""assertThatJson(parsedJson).array("errors").contains("message").isEqualTo("incorrect_format")""") + test.contains("""assertThatJson(parsedJson).array("errors").contains("property").isEqualTo("bank_account_number")""") + test.contains("""assertThatJson(parsedJson).array("errors").contains("message").isEqualTo("incorrect_format")""") and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should work properly with GString url with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { + GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'PUT' - url "/partners/${value(client(regex('^[0-9]*$')), server('11'))}/agents/11/customers/09665703Z" - headers { - header 'Content-Type': 'application/json' - } - body( - first_name: 'Josef', - ) - } - response { - status 422 + request { + method 'PUT' + url "/partners/${value(client(regex('^[0-9]*$')), server('11'))}/agents/11/customers/09665703Z" + headers { + header 'Content-Type': 'application/json' } + body( + first_name: 'Josef', + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 422 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('''/partners/11/agents/11/customers/09665703Z''') + test.contains('''/partners/11/agents/11/customers/09665703Z''') and: - stubMappingIsValidWireMockStub(contractDsl) + stubMappingIsValidWireMockStub(contractDsl) where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should resolve properties in GString with regular expression with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' - } - body( - email: $(client(regex(email())), server('not.existing@user.com')), - callback_url: $(client(regex(hostname())), server('http://partners.com')) - ) - } - response { - status 404 - headers { - header 'Content-Type': 'application/json' - } - body( - code: 4, - message: "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" - ) + GroovyDsl contractDsl = GroovyDsl.make { + priority 1 + request { + method 'POST' + url '/users/password' + headers { + header 'Content-Type': 'application/json' } + body( + email: $(client(regex(email())), server('not.existing@user.com')), + callback_url: $(client(regex(hostname())), server('http://partners.com')) + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 404 + headers { + header 'Content-Type': 'application/json' + } + body( + code: 4, + message: "User not found by email = [${value(server(regex(email())), client('not.existing@user.com'))}]" + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains("""assertThatJson(parsedJson).field("message").matches("User not found by email = \\\\\\\\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,4}\\\\\\\\]")""") + test.contains("""assertThatJson(parsedJson).field("message").matches("User not found by email = \\\\\\\\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,4}\\\\\\\\]")""") where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue('42') def "should not omit the optional field in the test creation with MockMvcSpockMethodBodyBuilder"() { given: - MethodBodyBuilder builder = new MockMvcSpockMethodRequestProcessingBodyBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + MethodBodyBuilder builder = new MockMvcSpockMethodRequestProcessingBodyBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('''"email":"abc@abc.com"''') - test.contains("""assertThatJson(parsedJson).field("code").matches("(123123)?")""") - !test.contains('''REGEXP''') - !test.contains('''OPTIONAL''') - !test.contains('''OptionalProperty''') + test.contains('''"email":"abc@abc.com"''') + test.contains("""assertThatJson(parsedJson).field("code").matches("(123123)?")""") + !test.contains('''REGEXP''') + !test.contains('''OPTIONAL''') + !test.contains('''OptionalProperty''') where: - contractDsl << [dslWithOptionals, dslWithOptionalsInString] + contractDsl << [dslWithOptionals, dslWithOptionalsInString] } @Issue('42') def "should not omit the optional field in the test creation with MockMvcJUnitMethodBodyBuilder"() { given: - MethodBodyBuilder builder = new MockMvcJUnitMethodBodyBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + MethodBodyBuilder builder = new MockMvcJUnitMethodBodyBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('\\"email\\":\\"abc@abc.com\\"') - test.contains('assertThatJson(parsedJson).field("code").matches("(123123)?");') - !test.contains('''REGEXP''') - !test.contains('''OPTIONAL''') - !test.contains('''OptionalProperty''') + test.contains('\\"email\\":\\"abc@abc.com\\"') + test.contains('assertThatJson(parsedJson).field("code").matches("(123123)?");') + !test.contains('''REGEXP''') + !test.contains('''OPTIONAL''') + !test.contains('''OptionalProperty''') where: - contractDsl << [dslWithOptionals, dslWithOptionalsInString] + contractDsl << [dslWithOptionals, dslWithOptionalsInString] } @Issue('72') def "should make the execute method work with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method """PUT""" - url """/fraudcheck""" - body(""" + GroovyDsl contractDsl = GroovyDsl.make { + request { + method """PUT""" + url """/fraudcheck""" + body(""" { - "clientPesel":"${value(client(regex('[0-9]{10}')), server('1234567890'))}", + "clientPesel":"${ + value(client(regex('[0-9]{10}')), server('1234567890')) + }", "loanAmount":123.123 } """ - ) - headers { - header("""Content-Type""", """application/vnd.fraud.v1+json""") - } - + ) + headers { + header("""Content-Type""", """application/vnd.fraud.v1+json""") } - response { - status 200 - body("""{ + + } + response { + status 200 + body("""{ "fraudCheckStatus": "OK", - "rejectionReason": ${value(client(null), server(execute('assertThatRejectionReasonIsNull($it)')))} + "rejectionReason": ${ + value(client(null), server(execute('assertThatRejectionReasonIsNull($it)'))) + } }""") - headers { - header('Content-Type': 'application/vnd.fraud.v1+json') - header 'Location': value( + headers { + header('Content-Type': 'application/vnd.fraud.v1+json') + header 'Location': value( stub(null), test(execute('assertThatLocationIsNull($it)')) - ) - } + ) } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - String test = blockBuilder.toString() + builder.appendTo(blockBuilder) + String test = blockBuilder.toString() then: - assertionStrings.each { String assertionString -> - assert test.contains(assertionString) - } + assertionStrings.each { String assertionString -> + assert test.contains(assertionString) + } where: - methodBuilderName | methodBuilder | assertionStrings - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ['''assertThatRejectionReasonIsNull(parsedJson.read('$.rejectionReason'))''', '''assertThatLocationIsNull(response.header('Location'))'''] - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ['''assertThatRejectionReasonIsNull(parsedJson.read("$.rejectionReason"))''', '''assertThatLocationIsNull(response.header("Location"))'''] + methodBuilderName | methodBuilder | assertionStrings + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ['''assertThatRejectionReasonIsNull(parsedJson.read('$.rejectionReason'))''', '''assertThatLocationIsNull(response.header('Location'))'''] + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ['''assertThatRejectionReasonIsNull(parsedJson.read("$.rejectionReason"))''', '''assertThatLocationIsNull(response.header("Location"))'''] } def "should support inner map and list definitions with #methodBuilderName"() { given: - Pattern PHONE_NUMBER = Pattern.compile(/[+\w]*/) - Pattern ANYSTRING = Pattern.compile(/.*/) - Pattern NUMBERS = Pattern.compile(/[\d\.]*/) - Pattern DATETIME = ANYSTRING + Pattern PHONE_NUMBER = Pattern.compile(/[+\w]*/) + Pattern ANYSTRING = Pattern.compile(/.*/) + Pattern NUMBERS = Pattern.compile(/[\d\.]*/) + Pattern DATETIME = ANYSTRING - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "PUT" - url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/client_data" - headers { - header('Content-Type': 'application/json') - } - body( - client: [ - first_name : $(stub(regex(onlyAlphaUnicode())), test('Denis')), - last_name : $(stub(regex(onlyAlphaUnicode())), test('FakeName')), - email : $(stub(regex(email())), test('fakemail@fakegmail.com')), - fax : $(stub(PHONE_NUMBER), test('+xx001213214')), - phone : $(stub(PHONE_NUMBER), test('2223311')), - data_of_birth: $(stub(DATETIME), test('2002-10-22T00:00:00Z')) - ], - client_id_card: [ - id : $(stub(ANYSTRING), test('ABC12345')), - date_of_issue: $(stub(ANYSTRING), test('2002-10-02T00:00:00Z')), - address : [ - street : $(stub(ANYSTRING), test('Light Street')), - city : $(stub(ANYSTRING), test('Fire')), - region : $(stub(ANYSTRING), test('Skys')), - country: $(stub(ANYSTRING), test('HG')), - zip : $(stub(NUMBERS), test('658965')) - ] - ], - incomes_and_expenses: [ - monthly_income : $(stub(NUMBERS), test('0.0')), - monthly_loan_repayments: $(stub(NUMBERS), test('100')), - monthly_living_expenses: $(stub(NUMBERS), test('22')) - ], - additional_info: [ - allow_to_contact: $(stub(optional(regex(anyBoolean()))), test('true')) - ] - ) + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "PUT" + url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/client_data" + headers { + header('Content-Type': 'application/json') } - response { - status 200 - headers { - header('Content-Type': 'application/json') - } + body( + client: [ + first_name : $(stub(regex(onlyAlphaUnicode())), test('Denis')), + last_name : $(stub(regex(onlyAlphaUnicode())), test('FakeName')), + email : $(stub(regex(email())), test('fakemail@fakegmail.com')), + fax : $(stub(PHONE_NUMBER), test('+xx001213214')), + phone : $(stub(PHONE_NUMBER), test('2223311')), + data_of_birth: $(stub(DATETIME), test('2002-10-22T00:00:00Z')) + ], + client_id_card: [ + id : $(stub(ANYSTRING), test('ABC12345')), + date_of_issue: $(stub(ANYSTRING), test('2002-10-02T00:00:00Z')), + address : [ + street : $(stub(ANYSTRING), test('Light Street')), + city : $(stub(ANYSTRING), test('Fire')), + region : $(stub(ANYSTRING), test('Skys')), + country: $(stub(ANYSTRING), test('HG')), + zip : $(stub(NUMBERS), test('658965')) + ] + ], + incomes_and_expenses: [ + monthly_income : $(stub(NUMBERS), test('0.0')), + monthly_loan_repayments: $(stub(NUMBERS), test('100')), + monthly_living_expenses: $(stub(NUMBERS), test('22')) + ], + additional_info: [ + allow_to_contact: $(stub(optional(regex(anyBoolean()))), test('true')) + ] + ) + } + response { + status 200 + headers { + header('Content-Type': 'application/json') } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains bodyString - !test.contains("clientValue") - !test.contains("cursor") + test.contains bodyString + !test.contains("clientValue") + !test.contains("cursor") where: - methodBuilderName | methodBuilder | bodyString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '"street":"Light Street"' - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '\\"street\\":\\"Light Street\\"' + methodBuilderName | methodBuilder | bodyString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | '"street":"Light Street"' + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '\\"street\\":\\"Light Street\\"' } def "shouldn't generate unicode escape characters with #methodBuilderName"() { given: - Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/) + Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/) - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "PUT" - url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/енев" - headers { - header('Content-Type': 'application/json') - } - body( - client: [ - first_name: $(stub(ONLY_ALPHA_UNICODE), test('Пенева')), - last_name : $(stub(ONLY_ALPHA_UNICODE), test('Пенева')) - ] - ) + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "PUT" + url "/v1/payments/e86df6f693de4b35ae648464c5b0dc09/енев" + headers { + header('Content-Type': 'application/json') } - response { - status 200 - headers { - header('Content-Type': 'application/json') - } + body( + client: [ + first_name: $(stub(ONLY_ALPHA_UNICODE), test('Пенева')), + last_name : $(stub(ONLY_ALPHA_UNICODE), test('Пенева')) + ] + ) + } + response { + status 200 + headers { + header('Content-Type': 'application/json') } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - !test.contains("\\u041f") + !test.contains("\\u041f") where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue('177') def "should generate proper test code when having multiline body with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "PUT" - url "/multiline" - body('''hello, + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "PUT" + url "/multiline" + body('''hello, World.''') - } - response { - status 200 - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.given(blockBuilder) - def test = blockBuilder.toString() + builder.given(blockBuilder) + def test = blockBuilder.toString() then: - test.contains(bodyString) + test.contains(bodyString) where: - methodBuilderName | methodBuilder | bodyString - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """'''hello, + methodBuilderName | methodBuilder | bodyString + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | """'''hello, World.'''""" - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '\\"hello,\\nWorld.\\"' + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | '\\"hello,\\nWorld.\\"' } @Issue('180') def "should generate proper test code when having multipart parameters with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "PUT" - url "/multipart" - headers { - header('content-type', 'multipart/form-data;boundary=AaB03x') - } - multipart( - formParameter: value(client(regex('.+')), server('"formParameterValue"')), - someBooleanParameter: value(client(regex('(true|false)')), server('true')), - file: named(value(client(regex('.+')), server('filename.csv')), value(client(regex('.+')), server('file content'))) - ) - } - response { - status 200 + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "PUT" + url "/multipart" + headers { + header('content-type', 'multipart/form-data;boundary=AaB03x') } + multipart( + formParameter: value(client(regex('.+')), server('"formParameterValue"')), + someBooleanParameter: value(client(regex('(true|false)')), server('true')), + file: named(value(client(regex('.+')), server('filename.csv')), value(client(regex('.+')), server('file content'))) + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - for (String requestString : requestStrings) { - test.contains(requestString) - } + for (String requestString : requestStrings) { + test.contains(requestString) + } where: - methodBuilderName | methodBuilder | requestStrings - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ["""'content-type', 'multipart/form-data;boundary=AaB03x'""", - """.param('formParameter', '"formParameterValue"'""", - """.param('someBooleanParameter', 'true')""", - """.multiPart('file', 'filename.csv', 'file content'.bytes)"""] - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ['"content-type", "multipart/form-data;boundary=AaB03x"', - '.param("formParameter", "\\"formParameterValue\\"")', - '.param("someBooleanParameter", "true")', - '.multiPart("file", "filename.csv", "file content".getBytes());'] + methodBuilderName | methodBuilder | requestStrings + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } | ["""'content-type', 'multipart/form-data;boundary=AaB03x'""", + """.param('formParameter', '"formParameterValue"'""", + """.param('someBooleanParameter', 'true')""", + """.multiPart('file', 'filename.csv', 'file content'.bytes)"""] + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } | ['"content-type", "multipart/form-data;boundary=AaB03x"', + '.param("formParameter", "\\"formParameterValue\\"")', + '.param("someBooleanParameter", "true")', + '.multiPart("file", "filename.csv", "file content".getBytes());'] } @Issue('180') def "should generate proper test code when having multipart parameters with named as map with #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "PUT" - url "/multipart" - multipart( - formParameter: value(client(regex('".+"')), server('"formParameterValue"')), - someBooleanParameter: value(client(regex('(true|false)')), server('true')), - file: named( - name: value(client(regex('.+')), server('filename.csv')), - content: value(client(regex('.+')), server('file content'))) - ) - } - response { - status 200 - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "PUT" + url "/multipart" + multipart( + formParameter: value(client(regex('".+"')), server('"formParameterValue"')), + someBooleanParameter: value(client(regex('(true|false)')), server('true')), + file: named( + name: value(client(regex('.+')), server('filename.csv')), + content: value(client(regex('.+')), server('file content'))) + ) } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.given(blockBuilder) - def test = blockBuilder.toString() + builder.given(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('.multiPart') + test.contains('.multiPart') where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } @Issue('#216') def "should parse JSON with arrays using #methodBuilderName"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method "GET" - urlPath('/auth/oauth/check_token') { - queryParameters { - parameter 'token': - value( - client(regex('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')), - server('6973b31d-7140-402a-bca6-1cdb954e03a7') - ) - } + GroovyDsl contractDsl = GroovyDsl.make { + request { + method "GET" + urlPath('/auth/oauth/check_token') { + queryParameters { + parameter 'token': + value( + client(regex('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')), + server('6973b31d-7140-402a-bca6-1cdb954e03a7') + ) } } - response { - status 200 - body( - authorities: [ - value(stub('ROLE_ADMIN'), test(regex('^[a-zA-Z0-9_\\- ]+$'))) - ] - ) - } } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + body( + authorities: [ + value(stub('ROLE_ADMIN'), test(regex('^[a-zA-Z0-9_\\- ]+$'))) + ] + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - test.contains('''assertThatJson(parsedJson).array("authorities").matches("^[a-zA-Z0-9_\\\\- ]+\\$").value()''') + test.contains('''assertThatJson(parsedJson).array("authorities").matches("^[a-zA-Z0-9_\\\\- ]+\\$").value()''') where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } def "should work with execution property"() { given: - GroovyDsl contractDsl = GroovyDsl.make { - request { - method 'PUT' - url '/fraudcheck' - } - response { - status 200 - body( - fraudCheckStatus: "OK", - rejectionReason: $(client(null), server(execute('assertThatRejectionReasonIsNull($it)'))) - ) - } - + GroovyDsl contractDsl = GroovyDsl.make { + request { + method 'PUT' + url '/fraudcheck' } - MethodBodyBuilder builder = methodBuilder(contractDsl) - BlockBuilder blockBuilder = new BlockBuilder(" ") + response { + status 200 + body( + fraudCheckStatus: "OK", + rejectionReason: $(client(null), server(execute('assertThatRejectionReasonIsNull($it)'))) + ) + } + + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") when: - builder.appendTo(blockBuilder) - def test = blockBuilder.toString() + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() then: - !test.contains('''assertThatJson(parsedJson).field("rejectionReason").isEqualTo("assertThatRejectionReasonIsNull("''') - test.contains('''assertThatRejectionReasonIsNull(''') + !test.contains('''assertThatJson(parsedJson).field("rejectionReason").isEqualTo("assertThatRejectionReasonIsNull("''') + test.contains('''assertThatRejectionReasonIsNull(''') where: - methodBuilderName | methodBuilder - "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } - "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) } + "MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) } } - - -} + GroovyDsl dslForDocs = + // tag::dsl_example[] + io.codearte.accurest.dsl.GroovyDsl.make { + request { + method 'PUT' + url '/api/12' + headers { + header 'Content-Type': 'application/vnd.com.ofg.twitter-places-analyzer.v1+json' + } + body '''\ + [{ + "created_at": "Sat Jul 26 09:38:57 +0000 2014", + "id": 492967299297845248, + "id_str": "492967299297845248", + "text": "Gonna see you at Warsaw", + "place": + { + "attributes":{}, + "bounding_box": + { + "coordinates": + [[ + [-77.119759,38.791645], + [-76.909393,38.791645], + [-76.909393,38.995548], + [-77.119759,38.995548] + ]], + "type":"Polygon" + }, + "country":"United States", + "country_code":"US", + "full_name":"Washington, DC", + "id":"01fbe706f872cb32", + "name":"Washington", + "place_type":"city", + "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json" + } + }] + ''' + } + response { + status 200 + } + } + // end::dsl_example[] + } \ No newline at end of file diff --git a/accurest-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/com/ofg/twitter/places/BaseMockMvcSpec.groovy b/accurest-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/com/ofg/twitter/places/BaseMockMvcSpec.groovy index 37f40e1ba9..4e94ba6525 100644 --- a/accurest-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/com/ofg/twitter/places/BaseMockMvcSpec.groovy +++ b/accurest-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/com/ofg/twitter/places/BaseMockMvcSpec.groovy @@ -4,6 +4,7 @@ import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc import com.ofg.twitter.place.PairIdController import spock.lang.Specification +// tag::base_class[] abstract class BaseMockMvcSpec extends Specification { def setup() { @@ -19,3 +20,4 @@ abstract class BaseMockMvcSpec extends Specification { } } +// end::base_class[] diff --git a/docs/src/docs/asciidoc/contract.adoc b/docs/src/docs/asciidoc/contract.adoc index 7e2d7f7c97..000082923e 100644 --- a/docs/src/docs/asciidoc/contract.adoc +++ b/docs/src/docs/asciidoc/contract.adoc @@ -1,65 +1,28 @@ == Contract DSL -Contract DSL in Accurest is written in Groovy, but don't be alarmed if you didn't use Groovy before. Knowledge of the language is not really needed as our DSL uses only a tiny subset of it (namely literals, method calls and closures). What's more, Accurest's DSL is designed to be programmer-readable without any knowledge of the DSL itself. +Contract DSL in Accurest is written in Groovy, but don't be alarmed if you didn't use Groovy before. Knowledge of the language is not really needed as our DSL uses only +a tiny subset of it (namely literals, method calls and closures). What's more, Accurest's DSL is designed to be programmer-readable without any knowledge of the DSL itself - + it's statically typed. Let's look at full example of a contract definition. + [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - method 'POST' - urlPath('/users') { - queryParameters { - parameter 'limit': 100 - parameter 'offset': containing("1") - parameter 'filter': "email" - } - } - headers { - header 'Content-Type': 'application/json' - } - body '''{ "login" : "john", "name": "John The Contract" }''' - } - response { - status 200 - headers { - header 'Location': '/users/john' - } - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=dsl_example,indent=0] ---- Not all features of the DSL are used in example above. If you didn't find what you are looking for, please check next paragraphs on this page. > You can easily compile Accurest Contracts to WireMock stubs mapping using standalone maven command: `mvn io.codearte.accurest:accurest-maven-plugin:convert`. -=== Top-Level Elements +=== HTTP Top-Level Elements Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - // Definition of HTTP request part of the contract - // (this can be a valid request or invalid depending - // on type of contract being specified). - request { - ... - } - - // Definition of HTTP response part of the contract - // (a service implementing this contract should respond - // with following response after receiving request - // specified in "request" part above). - response { - ... - } - - // Contract priority, which can be used for overriding - // contracts (1 is highest). Priority is optional. - priority 1 -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=http_dsl,indent=0] ---- === Request @@ -68,145 +31,43 @@ HTTP protocol requires only **method and address** to be specified in a request. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - // HTTP request method (GET/POST/PUT/DELETE). - method 'GET' - - // Path component of request URL is specified as follows. - urlPath('/users') - } - - response { - ... - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=request,indent=0] ---- It is possible to specify whole `url` instead of just path, but `urlPath` is the recommended way as it makes the tests **host-independent**. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - method 'GET' +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=url,indent=0] - // Specifying `url` and `urlPath` in one contract is illegal. - url('http://localhost:8888/users') - } - - response { - ... - } -} ---- Request may contain **query parameters**, which are specified in a closure nested in a call to `urlPath` or `url`. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - - urlPath('/users') { - - // Each parameter is specified in form - // `'paramName' : paramValue` where parameter value - // may be a simple literal or one of matcher functions, - // all of which are used in this example. - queryParameters { - - // If a simple literal is used as value - // default matcher function is used (equalTo) - parameter 'limit': 100 - - // `equalTo` function simply compares passed value - // using identity operator (==). - parameter 'filter': equalTo("email") - - // `containing` function matches strings - // that contains passed substring. - parameter 'gender': containing("[mf]") - - // `matching` function tests parameter - // against passed regular expression. - parameter 'offset': matching("[0-9]+") - - // `notMatching` functions tests if parameter - // does not match passed regular expression. - parameter 'loginStartsWith': notMatching(".{0,2}") - } - } - - ... - } - - response { - ... - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=urlpath,indent=0] ---- It may contain additional **request headers**... [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - - // Each header is added in form `'Header-Name' : 'Header-Value'`. - headers { - header 'Content-Type': 'application/json' - } - - ... - } - - response { - ... - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=headers,indent=0] ---- ...and a **request body**. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - - // JSON and XML formats of request body are supported. - // Format will be determined from a header or body's content. - body '''{ "login" : "john", "name": "John The Contract" }''' - } - - response { - ... - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=body,indent=0] ---- **Body's format** can also be specified explicitly by invoking one of format functions. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - - // In this case body will be formatted as XML. - body equalToXml( - '''johnJohn The Contract''' - ) - } - - response { - ... - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=bodyAsXml,indent=0] ---- === Response @@ -215,54 +76,21 @@ Minimal response must contain **HTTP status code**. [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - } - response { - // Status code sent by the server - // in response to request specified above. - status 200 - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=response,indent=0] ---- Besides status response may contain **headers** and **body**, which are specified the same way as in the request (see previous paragraph). === Regular expressions -You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both for your test and your server side tests. +You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response +should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both +for your test and your server side tests. - Please see the example below: +Please see the example below: [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl groovyDsl == GroovyDsl.make { - request { - method('GET') - url $(client(~/\/[0-9]{2}/), server('/12')) - } - response { - status 200 - body( - id: value( - client('123'), - server(regex('[0-9]+')) - ), - surname: $( - client('Kowalsky'), - server('Lewandowski') - ), - name: 'Jan', - created: $(client('2014-02-02 12:23:43'), server({ currentDate(it) })) - correlationId: value(client('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'), - server(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}') - ) - ) - headers { - header 'Content-Type': 'text/plain' - } - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=regex,indent=0] ---- === Passing optional parameters @@ -276,131 +104,41 @@ Example: [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - priority 1 - request { - method 'POST' - url '/users/password' - headers { - header 'Content-Type': 'application/json' - } - body( - email: $(stub(optional(regex(email()))), test('abc@abc.com')), - callback_url: $(stub(regex(hostname())), test('http://partners.com')) - ) - } - response { - status 404 - headers { - header 'Content-Type': 'application/json' - } - body( - code: value(stub("123123"), test(optional("123123"))), - message: "User not found by email == [${value(test(regex(email())), stub('not.existing@user.com'))}]" - ) - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=optionals,indent=0] ---- By wrapping a part of the body with the `optional()` method you are in fact creating a regular expression that should be present 0 or more times. -That way for the example above the following test would be generated: +That way for the example above the following test would be generated if you pick Spock: [source,groovy,indent=0] ---- - given: - def request == given() - .header('Content-Type', 'application/json') - .body('{"email":"abc@abc.com","callback_url":"http://partners.com"}') - - when: - def response == given().spec(request) - .post("/users/password") - - then: - response.statusCode === 404 - response.header('Content-Type') === 'application/json' - and: - DocumentContext parsedJson == JsonPath.parse(response.body.asString()) - !parsedJson.read('''$[?(@.code =~ /(123123)?/)]''', JSONArray).empty - !parsedJson.read('''$[?(@.message =~ /User not found by email == \\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}\\]/)]''', JSONArray).empty - +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=optionals_test,indent=0] ---- and the following stub: [source,javascript,indent=0] ---- -{ - "request" : { - "url" : "/users/password", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?/)]" - }, { - "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4})?/)]" - } ], - "headers" : { - "Content-Type" : { - "equalTo" : "application/json" - } - } - }, - "response" : { - "status" : 404, - "body" : "{\"code\":\"123123\",\"message\":\"User not found by email == [not.existing@user.com]\"}", - "headers" : { - "Content-Type" : "application/json" - } - }, - "priority" : 1 -} +include::../../../../accurest-converters/src/test/groovy/io/codearte/accurest/wiremock/DslToWireMockClientConverterSpec.groovy[tags=wiremock,indent=0] ---- === Executing custom methods on server side -It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" in the configuration. Please see the examples below: +It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" +in the configuration. Please see the examples below: ==== Groovy DSL [source,groovy,indent=0] ---- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - method 'PUT' - url $(client(regex('^/api/[0-9]{2}$')), server('/api/12')) - headers { - header 'Content-Type': 'application/json' - } - body '''\ - [{ - "text": "Gonna see you at Warsaw" - }] -''' - } - response { - body ( - path: $(client('/api/12'), server(regex('^/api/[0-9]{2}$'))), - correlationId: $(client('1223456'), server(execute('isProperCorrelationId($it)'))) - ) - status 200 - } -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/ContractHttpDocsSpec.groovy[tags=method,indent=0] ---- ==== Base Mock Spec [source,groovy,indent=0] ---- -abstract class BaseMockMvcSpec extends Specification { - - def setup() { - RestAssuredMockMvc.standaloneSetup(new PairIdController()) - } - - void isProperCorrelationId(Integer correlationId) { - assert correlationId === 123456 - } -} +include::../../../../accurest-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/com/ofg/twitter/places/BaseMockMvcSpec.groovy[tags=base_class,indent=0] ---- === JAX-RS support @@ -419,43 +157,5 @@ Example of a test API generated: [source,groovy,indent=0] ---- -class FraudDetectionServiceSpec extends MvcSpec { - - def shouldMarkClientAsNotFraud() { - when: - def response == webTarget - .path('/fraudcheck') - .request() - .method('put', entity('{"clientPesel":"1234567890","loanAmount":123.123}', 'application/vnd.fraud.v1+json')) - - String responseAsString == response.readEntity(String) - - then: - response.status === 200 - response.getHeaderString('Content-Type') === 'application/vnd.fraud.v1+json' - and: - def responseBody == new JsonSlurper().parseText(responseAsString) - responseBody.fraudCheckStatus === "OK" - assertThatRejectionReasonIsNull(responseBody.rejectionReason) - } - - def shouldMarkClientAsFraud() { - when: - def response == webTarget - .path('/fraudcheck') - .request() - .method('put', entity('{"clientPesel":"1234567890","loanAmount":99999}', 'application/vnd.fraud.v1+json')) - - String responseAsString == response.readEntity(String) - - then: - response.status === 200 - response.getHeaderString('Content-Type') === 'application/vnd.fraud.v1+json' - and: - def responseBody == new JsonSlurper().parseText(responseAsString) - responseBody.fraudCheckStatus ==~ java.util.regex.Pattern.compile('[A-Z]{5}') - responseBody.rejectionReason === "Amount too high" - } - -} +include::../../../../accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy[tags=jaxrs,indent=0] ---- \ No newline at end of file diff --git a/docs/src/docs/asciidoc/images/Deps.png b/docs/src/docs/asciidoc/images/Deps.png new file mode 100644 index 0000000000000000000000000000000000000000..1426814308101b8ccf6ef95fd195e37c0a180392 GIT binary patch literal 37192 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 A(f|Me literal 0 HcmV?d00001 diff --git a/docs/src/docs/asciidoc/images/Stubs1.png b/docs/src/docs/asciidoc/images/Stubs1.png new file mode 100644 index 0000000000000000000000000000000000000000..ebadfdb91014284b917367e3d9c9c8a137da3993 GIT binary patch literal 35170 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 literal 0 HcmV?d00001 diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index a1d3a7eb8b..060f611643 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -2,36 +2,14 @@ Welcome to the Accurest Documentation! include::introduction.adoc[] -include::rest.adoc[] - include::contract.adoc[] +include::rest.adoc[] + include::messaging.adoc[] include::stubrunner.adoc[] include::stubrunner_msg.adoc[] -== Migration Guide - -=== Migration to 0.4.7 -- in 0.4.7 we've fixed package name (coderate to codearte) so you've to do the same in your projects. This means replacing ```io.coderate.accurest.dsl.GroovyDsl``` with ```io.codearte.accurest.dsl.GroovyDsl``` - -=== Migration to 1.0.0-RC1 -- from 1.0.0 we're distinguish ignored contracts from excluded contracts: - - `excludedFiles` pattern tells Accurest to skip processing those files at all - - `ignoredFiles` pattern tells Accurest to generate contracts and tests, but tests will be marked as `@Ignore` - -- from 1.0.0 the `basePackageForTests` behaviour has changed - - prior to the change all DSL files had to be under `contractsDslDir`/`basePackageForTests`/*subpackage* resulting in `basePackageForTests`.*subpackage* test package creation - - now all DSL files have to be under `contractsDslDir`/*subpackage* resulting in `basePackageForTests`.*subpackage* test package creation - - If you don't migrate to the new approach you will have your tests under `contractsDslDir`.`contractsDslDir`.*subpackage* - -=== Migration to 1.0.7 -- from 1.0.7 we're setting JUnit as a default testing utility. You have to pass the following option to keep Spock -as your first choice: - -[source,groovy] ----- -include::../../../../accurest-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=target_framework,indent=0] ----- \ No newline at end of file +include::migration.adoc[] \ No newline at end of file diff --git a/docs/src/docs/asciidoc/introduction.adoc b/docs/src/docs/asciidoc/introduction.adoc index 6714efb910..f7ac2b0c17 100644 --- a/docs/src/docs/asciidoc/introduction.adoc +++ b/docs/src/docs/asciidoc/introduction.adoc @@ -1,26 +1,80 @@ == Introduction -Just to make long story short - Accurest is a tool that enables Consumer Driven Contract (CDC) development of JVM-based applications. It is shipped with __REST Contract Definition Language__ (DSL). Contract definitions are used by Accurest to produce following resources: +Just to make long story short - Accurest is a tool that enables Consumer Driven Contract (CDC) development of JVM-based applications. It is shipped +with __Contract Definition Language__ (DSL). Contract definitions are used by Accurest to produce following resources: -* JSON stub definitions to be used by Wiremock when doing integration testing on the client code (__client tests__). Test code must still be written by hand, test data is produced by Accurest. -* Acceptance tests (in Spock) used to verify if server-side implementation of the API is compliant with the contract (__server tests__). Full test is generated by Accurest. +* JSON stub definitions to be used by Wiremock when doing integration testing on the client code (__client tests__). +Test code must still be written by hand, test data is produced by Accurest. +* Messaging routes if you're using one. We're integrating with Spring Integration, Spring Cloud Stream and Apache Camel. You can however set your own integrations if you want to +* Acceptance tests (in JUnit or Spock) used to verify if server-side implementation of the API is compliant with the contract (__server tests__). Full test is generated by Accurest. Accurest moves TDD to the level of software architecture. === Why? -The main purposes of Accurest are: +Let us assume that we have a system comprising of multiple microservices: - - to ensure that WireMock stubs (used when developing the client) are doing exactly what actual server-side implementation will do, +image::Deps.png[Microservices Architecture] + +==== Testing issues + +If we wanted to test the application in top left corner if it can communicate with other services then we could do one of two things: + +- deploy all microservices and perform end to end tests +- mock other microservices in unit / integration tests + +Both have their advantages but also a lot of disadvantages. Let's focus on the latter. + +*Deploy all microservices and perform end to end tests* + +Advantages: +- simulates production +- tests real communication between services + +Disadvantages: +- to test one microservice we would have to deploy 6 microservices, a couple of databases etc. +- the environment where the tests would be conducted would be locked for a single suite of tests (i.e. nobody else would be able to run the tests in the meantime). +- long to run +- very late feedback +- extremely hard to debug + +*Mock other microservices in unit / integration tests* + +Advantages: +- very fast feedback +- no infrastructure requirements + +Disadvantages: +- the implementor of the service creates stubs thus they might have nothing to do with the reality +- you can go to production with passing tests and failing production + +To solve the aforementioned issues Accurest with Stub Runner were created. Their main idea is to give you very fast feedback, without the need +to set up the whole world of microservices. + +image::Stubs1.png[Stubbed Services] + +If you work on stubs then the only applications you need are those that your application is using directly. + +image::Stubs2.png[Stubbed Services] + +Accurest gives you the certainty that the stubs that you're using were created by the service that you're calling. Also if you can use them it means that they were +tested against the producer's side. In other words - you can trust those stubs. + + +=== Purposes + +The main purposes of Accurest with Stub Runner are: + + - to ensure that WireMock / Messaging stubs (used when developing the client) are doing exactly what actual server-side implementation will do, - to promote ATDD method and Microservices architectural style, - to provide a way to publish changes in contracts that are immediately visible on both sides, - to generate boilerplate test code used on the server side. === Client Side -During the tests you want to have a Wiremock instance up and running that simulates the service Y. +During the tests you want to have a Wiremock instance / Messaging route up and running that simulates the service Y. You would like to feed that instance with a proper stub definition. That stub definition would need -to be valid from the Wiremock's perspective but should also be reusable on the server side. +to be valid and should also be reusable on the server side. __Summing it up:__ On this side, in the stub definition, you can use patterns for request stubbing and you need exact values for responses. @@ -37,52 +91,20 @@ that your application behaves in the same way as you define in your stub. __Summing it up:__ On this side, in the stub definition, you need exact values as request and can use patterns/methods for response verification. -=== Examples +=== Dependencies -[source,groovy,indent=0] ----- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - method 'PUT' - url '/api/12' - headers { - header 'Content-Type': 'application/vnd.com.ofg.twitter-places-analyzer.v1+json' - } - body '''\ - [{ - "created_at": "Sat Jul 26 09:38:57 +0000 2014", - "id": 492967299297845248, - "id_str": "492967299297845248", - "text": "Gonna see you at Warsaw", - "place": - { - "attributes":{}, - "bounding_box": - { - "coordinates": - [[ - [-77.119759,38.791645], - [-76.909393,38.791645], - [-76.909393,38.995548], - [-77.119759,38.995548] - ]], - "type":"Polygon" - }, - "country":"United States", - "country_code":"US", - "full_name":"Washington, DC", - "id":"01fbe706f872cb32", - "name":"Washington", - "place_type":"city", - "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json" - } - }] -''' - } - response { - status 200 - } -} ----- +Accurest and Stub Runner are using the following libraries -//TODO: Add videos, slides \ No newline at end of file +- http://wiremock.org/[WireMock] +- https://github.com/jayway/JsonPath[Jayway JSONPath] +- https://github.com/marcingrzejszczak/jsonassert[JSONAssert from Marcin Grzejszczak] + +=== Additional readings / videos + +Below you can find some resources related to Accurest and Stub Runner. Note that some can be outdated since the Accurest project +is under constant development. + +- https://www.youtube.com/watch?v=daafmTYFoDU[Olga Maciaszek-Sharma talking about Accurest] +- https://vimeo.com/130779882[Marcin Grzejszczak and Jakub Kubrynski talking about Accurest] +- http://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest] +- http://toomuchcoding.com/blog/categories/accurest/[Accurest article from Marcin Grzejszczak's blog] diff --git a/docs/src/docs/asciidoc/migration.adoc b/docs/src/docs/asciidoc/migration.adoc new file mode 100644 index 0000000000..8ddc214619 --- /dev/null +++ b/docs/src/docs/asciidoc/migration.adoc @@ -0,0 +1,23 @@ +== Migration Guide + +=== Migration to 0.4.7 +- in 0.4.7 we've fixed package name (coderate to codearte) so you've to do the same in your projects. This means replacing ```io.coderate.accurest.dsl.GroovyDsl``` with ```io.codearte.accurest.dsl.GroovyDsl``` + +=== Migration to 1.0.0-RC1 +- from 1.0.0 we're distinguish ignored contracts from excluded contracts: + - `excludedFiles` pattern tells Accurest to skip processing those files at all + - `ignoredFiles` pattern tells Accurest to generate contracts and tests, but tests will be marked as `@Ignore` + +- from 1.0.0 the `basePackageForTests` behaviour has changed + - prior to the change all DSL files had to be under `contractsDslDir`/`basePackageForTests`/*subpackage* resulting in `basePackageForTests`.*subpackage* test package creation + - now all DSL files have to be under `contractsDslDir`/*subpackage* resulting in `basePackageForTests`.*subpackage* test package creation + - If you don't migrate to the new approach you will have your tests under `contractsDslDir`.`contractsDslDir`.*subpackage* + +=== Migration to 1.0.7 +- from 1.0.7 we're setting JUnit as a default testing utility. You have to pass the following option to keep Spock +as your first choice: + +[source,groovy] +---- +include::../../../../accurest-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=target_framework,indent=0] +---- \ No newline at end of file