diff --git a/README.md b/README.md index e86e47bb..9bacefee 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,39 @@ be written: - `index/response.asciidoc` - `index/request-response.asciidoc` +#### Parameterized output directories + +The `document` method supports parameterized output directories. The following parameters +are supported: + +| Parameter | Description +| ------------- | ----------- +| {methodName} | The name of the test method, formatted using camelcase +| {method-name} | The name of the test method, formatted with dash separators +| {method_name} | The name of the test method, formatted with underscore separators +| {step} | The count of calls to `MockMvc.perform` in the current test + +For example, `document("{method-name}")` in a test method named `creatingANote` will +write snippets into a directory named `creating-a-note`. + +The `{step}` parameter is particularly useful in combination with Spring MVC Test's +`alwaysDo` functionality. It allows documentation to be configured once in a setup method: + +```java +@Before +public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfigurer()) + .alwaysDo(document("{method-name}/{step}/")) + .build(); +} +``` + +With this configuration in place, every call to `MockMvc.perform` will produce +documentation snippets without any further configuration. Take a look at the +`GettingStartedDocumentation` classes in each of the sample applications to see this +functionality in action. + #### Pretty-printed snippets To improve the readability of the generated snippets you may want to configure your diff --git a/build.gradle b/build.gradle index 18ac9bd3..380f5559 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ project(':spring-restdocs') { junitVersion = '4.11' servletApiVersion = '3.1.0' springVersion = '4.1.4.RELEASE' + mockitoVersion = '1.10.19' } group = 'org.springframework.restdocs' @@ -63,6 +64,7 @@ project(':spring-restdocs') { compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" jacoco "org.jacoco:org.jacoco.agent:$jacocoVersion:runtime" testCompile "org.springframework:spring-webmvc:$springVersion" + testCompile "org.mockito:mockito-core:$mockitoVersion" } test { diff --git a/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.asciidoc b/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.asciidoc index 5f8e71db..e7781f24 100644 --- a/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.asciidoc +++ b/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.asciidoc @@ -42,12 +42,12 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/request.asciidoc[] +include::{generated}/index/1/request.asciidoc[] This request should yield the following response in the http://stateless.co/hal_specification.html[Hypertext Application Language (HAL)] format: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -59,26 +59,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] To create a note, you need to execute a `POST` request to this URI including a JSON payload containing the title and body of the note: -include::{generated}/create-note/request.asciidoc[] +include::{generated}/creating-a-note/1/request.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/create-note/response.asciidoc[] +include::{generated}/creating-a-note/1/response.asciidoc[] To work with the newly created note you use the URI in the `Location` header. For example, you can access the note's details by performing a `GET` request: -include::{generated}/get-note/request.asciidoc[] +include::{generated}/creating-a-note/2/request.asciidoc[] This request will produce a response with the note's details in its body: -include::{generated}/get-note/response.asciidoc[] +include::{generated}/creating-a-note/2/response.asciidoc[] Note the `tags` link which we'll make use of later. @@ -92,26 +92,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/create-tag/request.asciidoc[] +include::{generated}/creating-a-note/3/request.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/create-tag/response.asciidoc[] +include::{generated}/creating-a-note/3/response.asciidoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/get-tag/request.asciidoc[] +include::{generated}/creating-a-note/4/request.asciidoc[] This request will produce a response with the tag's details in its body: -include::{generated}/get-tag/response.asciidoc[] +include::{generated}/creating-a-note/4/response.asciidoc[] @@ -132,24 +132,24 @@ with it. Once again we execute a `POST` request. However, this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/create-tagged-note/request.asciidoc[] +include::{generated}/creating-a-note/5/request.asciidoc[] Once again, the response's `Location` header tells us the URI of the newly created note: -include::{generated}/create-tagged-note/response.asciidoc[] +include::{generated}/creating-a-note/5/response.asciidoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/get-tagged-note/request-response.asciidoc[] +include::{generated}/creating-a-note/6/request-response.asciidoc[] To verify that the tag has been associated with the note, we can perform a `GET` request against the URI from the `tags` link: -include::{generated}/get-tags/request.asciidoc[] +include::{generated}/creating-a-note/7/request.asciidoc[] The response embeds information about the tag that we've just associated with the note: -include::{generated}/get-tags/response.asciidoc[] +include::{generated}/creating-a-note/7/response.asciidoc[] @@ -159,17 +159,17 @@ An existing note can be tagged by executing a `PATCH` request against the note's a body that contains the array of tags to be associated with the note. We'll used the URI of the untagged note that we created earlier: -include::{generated}/tag-existing-note/request.asciidoc[] +include::{generated}/creating-a-note/8/request.asciidoc[] This request should produce a `204 No Content` response: -include::{generated}/tag-existing-note/response.asciidoc[] +include::{generated}/creating-a-note/8/response.asciidoc[] When we first created this note, we noted the tags link included in its details: -include::{generated}/get-note/response.asciidoc[] +include::{generated}/creating-a-note/2/response.asciidoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/get-tags-for-existing-note/request-response.asciidoc[] +include::{generated}/creating-a-note/9/request-response.asciidoc[] diff --git a/samples/rest-notes-spring-data-rest/src/main/resources/application.properties b/samples/rest-notes-spring-data-rest/src/main/resources/application.properties new file mode 100644 index 00000000..8e06a828 --- /dev/null +++ b/samples/rest-notes-spring-data-rest/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.jackson.serialization.indent_output: true \ No newline at end of file diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java index 55fe34e8..e5902a61 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java @@ -39,7 +39,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.RestDocumentationConfigurer; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java index ec54ff5d..1657af9a 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.RestDocumentationConfigurer; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -66,33 +66,33 @@ public class GettingStartedDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()).build(); + .apply(new RestDocumentationConfigurer()) + .alwaysDo(document("{method-name}/{step}/")) + .build(); } @Test public void index() throws Exception { this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("_links.notes", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(document("index")); + .andExpect(status().isOk()) + .andExpect(jsonPath("_links.notes", is(notNullValue()))) + .andExpect(jsonPath("_links.tags", is(notNullValue()))); } @Test public void creatingANote() throws JsonProcessingException, Exception { String noteLocation = createNote(); - getNote(noteLocation); + MvcResult note = getNote(noteLocation); String tagLocation = createTag(); getTag(tagLocation); String taggedNoteLocation = createTaggedNote(tagLocation); - getTaggedNote(taggedNoteLocation); - getTags(taggedNoteLocation); + MvcResult taggedNote = getNote(taggedNoteLocation); + getTags(getLink(taggedNote, "tags")); tagExistingNote(noteLocation, tagLocation); - getTaggedExistingNote(noteLocation); - getTagsForExistingNote(noteLocation); + getTags(getLink(note, "tags")); } String createNote() throws Exception { @@ -106,18 +106,17 @@ public class GettingStartedDocumentation { objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-note")) .andReturn().getResponse().getHeader("Location"); return noteLocation; } - void getNote(String noteLocation) throws Exception { - this.mockMvc.perform(get(noteLocation)) - .andExpect(status().isOk()) - .andExpect(jsonPath("title", is(notNullValue()))) - .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(document("get-note")); + MvcResult getNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)) + .andExpect(status().isOk()) + .andExpect(jsonPath("title", is(notNullValue()))) + .andExpect(jsonPath("body", is(notNullValue()))) + .andExpect(jsonPath("_links.tags", is(notNullValue()))) + .andReturn(); } String createTag() throws Exception, JsonProcessingException { @@ -128,19 +127,16 @@ public class GettingStartedDocumentation { .perform( post("/tags").contentType(MediaTypes.HAL_JSON).content( objectMapper.writeValueAsString(tag))) - .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-tag")) - .andReturn().getResponse().getHeader("Location"); + .andExpect(status().isCreated()) + .andExpect(header().string("Location", notNullValue())) + .andReturn().getResponse().getHeader("Location"); return tagLocation; } void getTag(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation)) - .andExpect(status().isOk()) + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("name", is(notNullValue()))) - .andExpect(jsonPath("_links.notes", is(notNullValue()))) - .andDo(document("get-tag")); + .andExpect(jsonPath("_links.notes", is(notNullValue()))); } String createTaggedNote(String tag) throws Exception { @@ -155,28 +151,14 @@ public class GettingStartedDocumentation { objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-tagged-note")) .andReturn().getResponse().getHeader("Location"); return noteLocation; } - void getTaggedNote(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation)) + void getTags(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) - .andExpect(jsonPath("title", is(notNullValue()))) - .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(document("get-tagged-note")); - } - - void getTags(String taggedNoteLocation) throws Exception { - String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) - .andReturn(), "tags"); - - this.mockMvc.perform(get(tagsLocation)) - .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(document("get-tags")); + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } void tagExistingNote(String noteLocation, String tagLocation) throws Exception { @@ -186,29 +168,24 @@ public class GettingStartedDocumentation { this.mockMvc.perform( patch(noteLocation).contentType(MediaTypes.HAL_JSON).content( objectMapper.writeValueAsString(update))) - .andExpect(status().isNoContent()) - .andDo(document("tag-existing-note")); - + .andExpect(status().isNoContent()); } - void getTaggedExistingNote(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation)) + MvcResult getTaggedExistingNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)) .andExpect(status().isOk()) - .andDo(document("get-tagged-existing-note")); + .andReturn(); } - void getTagsForExistingNote(String taggedNoteLocation) throws Exception { - String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) - .andReturn(), "tags"); - this.mockMvc.perform(get(tagsLocation)) + void getTagsForExistingNote(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(document("get-tags-for-existing-note")); + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } - private String getLink(MvcResult result, String href) + private String getLink(MvcResult result, String rel) throws UnsupportedEncodingException { return JsonPath.parse(result.getResponse().getContentAsString()).read( - "_links.tags.href"); + "_links." + rel + ".href"); } } diff --git a/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.asciidoc b/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.asciidoc index ce970ea9..8fccc65c 100644 --- a/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.asciidoc +++ b/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.asciidoc @@ -42,11 +42,11 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/request.asciidoc[] +include::{generated}/index/1/request.asciidoc[] This request should yield the following response: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -58,26 +58,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] To create a note you need to execute a `POST` request to this URI, including a JSON payload containing the title and body of the note: -include::{generated}/create-note/request.asciidoc[] +include::{generated}/creating-a-note/1/request.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/create-note/response.asciidoc[] +include::{generated}/creating-a-note/1/response.asciidoc[] To work with the newly created note you use the URI in the `Location` header. For example you can access the note's details by performing a `GET` request: -include::{generated}/get-note/request.asciidoc[] +include::{generated}/creating-a-note/2/request.asciidoc[] This request will produce a response with the note's details in its body: -include::{generated}/get-note/response.asciidoc[] +include::{generated}/creating-a-note/2/response.asciidoc[] Note the `note-tags` link which we'll make use of later. @@ -91,26 +91,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/response.asciidoc[] +include::{generated}/index/1/response.asciidoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/create-tag/request.asciidoc[] +include::{generated}/creating-a-note/3/request.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/create-tag/response.asciidoc[] +include::{generated}/creating-a-note/3/response.asciidoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/get-tag/request.asciidoc[] +include::{generated}/creating-a-note/4/request.asciidoc[] This request will produce a response with the tag's details in its body: -include::{generated}/get-tag/response.asciidoc[] +include::{generated}/creating-a-note/4/response.asciidoc[] @@ -131,44 +131,44 @@ with it. Once again we execute a `POST` request, but this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/create-tagged-note/request.asciidoc[] +include::{generated}/creating-a-note/5/request.asciidoc[] Once again, the response's `Location` header tells use the URI of the newly created note: -include::{generated}/create-tagged-note/response.asciidoc[] +include::{generated}/creating-a-note/5/response.asciidoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/get-tagged-note/request-response.asciidoc[] +include::{generated}/creating-a-note/6/request-response.asciidoc[] To see the note's tags, execute a `GET` request against the URI of the note's `note-tags` link: -include::{generated}/get-tags/request.asciidoc[] +include::{generated}/creating-a-note/7/request.asciidoc[] The response shows that, as expected, the note has a single tag: -include::{generated}/get-tags/response.asciidoc[] +include::{generated}/creating-a-note/7/response.asciidoc[] [getting-started-tagging-a-note-existing] === Tagging an existing note An existing note can be tagged by executing a `PATCH` request against the note's URI with -a body that contains the array of tags to be associated with the note. We'll used the +a body that contains the array of tags to be associated with the note. We'll use the URI of the untagged note that we created earlier: -include::{generated}/tag-existing-note/request.asciidoc[] +include::{generated}/creating-a-note/8/request.asciidoc[] This request should produce a `204 No Content` response: -include::{generated}/tag-existing-note/response.asciidoc[] +include::{generated}/creating-a-note/8/response.asciidoc[] When we first created this note, we noted the `note-tags` link included in its details: -include::{generated}/get-note/response.asciidoc[] +include::{generated}/creating-a-note/2/response.asciidoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/get-tags-for-existing-note/request-response.asciidoc[] +include::{generated}/creating-a-note/9/request-response.asciidoc[] diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java index d82451c8..bcb731bf 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java @@ -39,7 +39,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.RestDocumentationConfigurer; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java index 49892b52..5b95a35a 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -39,7 +38,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.RestDocumentationConfigurer; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -67,7 +66,9 @@ public class GettingStartedDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()).build(); + .apply(new RestDocumentationConfigurer()) + .alwaysDo(document("{method-name}/{step}/")) + .build(); } @Test @@ -75,25 +76,23 @@ public class GettingStartedDocumentation { this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("_links.notes", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(document("index")); + .andExpect(jsonPath("_links.tags", is(notNullValue()))); } @Test public void creatingANote() throws JsonProcessingException, Exception { String noteLocation = createNote(); - getNote(noteLocation); + MvcResult note = getNote(noteLocation); String tagLocation = createTag(); getTag(tagLocation); String taggedNoteLocation = createTaggedNote(tagLocation); - getTaggedNote(taggedNoteLocation); - getTags(taggedNoteLocation); + MvcResult taggedNote = getNote(taggedNoteLocation); + getTags(getLink(taggedNote, "note-tags")); tagExistingNote(noteLocation, tagLocation); - getTaggedExistingNote(noteLocation); - getTagsForExistingNote(noteLocation); + getTags(getLink(note, "note-tags")); } String createNote() throws Exception { @@ -107,17 +106,17 @@ public class GettingStartedDocumentation { objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-note")) .andReturn().getResponse().getHeader("Location"); return noteLocation; } - void getNote(String noteLocation) throws Exception { - this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk()) + MvcResult getNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)) + .andExpect(status().isOk()) .andExpect(jsonPath("title", is(notNullValue()))) .andExpect(jsonPath("body", is(notNullValue()))) .andExpect(jsonPath("_links.note-tags", is(notNullValue()))) - .andDo(document("get-note")); + .andReturn(); } String createTag() throws Exception, JsonProcessingException { @@ -130,7 +129,6 @@ public class GettingStartedDocumentation { objectMapper.writeValueAsString(tag))) .andExpect(status().isCreated()) .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-tag")) .andReturn().getResponse().getHeader("Location"); return tagLocation; } @@ -138,8 +136,7 @@ public class GettingStartedDocumentation { void getTag(String tagLocation) throws Exception { this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("name", is(notNullValue()))) - .andExpect(jsonPath("_links.tagged-notes", is(notNullValue()))) - .andDo(document("get-tag")); + .andExpect(jsonPath("_links.tagged-notes", is(notNullValue()))); } String createTaggedNote(String tag) throws Exception { @@ -154,28 +151,14 @@ public class GettingStartedDocumentation { objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) .andExpect(header().string("Location", notNullValue())) - .andDo(document("create-tagged-note")) .andReturn().getResponse().getHeader("Location"); return noteLocation; } - void getTaggedNote(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation)) + void getTags(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) - .andExpect(jsonPath("title", is(notNullValue()))) - .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("_links.note-tags", is(notNullValue()))) - .andDo(document("get-tagged-note")); - } - - void getTags(String taggedNoteLocation) throws Exception { - String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) - .andReturn(), "note-tags"); - this.mockMvc.perform(get(tagsLocation)) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(document("get-tags")); + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } void tagExistingNote(String noteLocation, String tagLocation) throws Exception { @@ -185,24 +168,19 @@ public class GettingStartedDocumentation { this.mockMvc.perform( patch(noteLocation).contentType(MediaTypes.HAL_JSON).content( objectMapper.writeValueAsString(update))) - .andExpect(status().isNoContent()) - .andDo(document("tag-existing-note")); - + .andExpect(status().isNoContent()); } - void getTaggedExistingNote(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation)) + MvcResult getTaggedExistingNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)) .andExpect(status().isOk()) - .andDo(document("get-tagged-existing-note")); + .andReturn(); } - void getTagsForExistingNote(String taggedNoteLocation) throws Exception { - String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) - .andReturn(), "note-tags"); - this.mockMvc.perform(get(tagsLocation)) + void getTagsForExistingNote(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(document("get-tags-for-existing-note")); + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } private String getLink(MvcResult result, String rel) diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java similarity index 93% rename from spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationConfigurer.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java index f644cbbd..44f80511 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs; +package org.springframework.restdocs.config; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.request.RequestPostProcessor; @@ -100,6 +100,11 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter { @Override public MockHttpServletRequest postProcessRequest( MockHttpServletRequest request) { + RestDocumentationContext currentContext = RestDocumentationContext + .currentContext(); + if (currentContext != null) { + currentContext.getAndIncrementStepCount(); + } request.setScheme(RestDocumentationConfigurer.this.scheme); request.setRemotePort(RestDocumentationConfigurer.this.port); request.setServerPort(RestDocumentationConfigurer.this.port); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java new file mode 100644 index 00000000..8d409952 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.config; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * {@code RestDocumentationContext} encapsulates the context in which the documentation of + * a RESTful API is being performed. + * + * @author Andy Wilkinson + */ +public class RestDocumentationContext { + + private static final ThreadLocal CONTEXTS = new InheritableThreadLocal(); + + private final AtomicInteger stepCount = new AtomicInteger(0); + + private final Method testMethod; + + private RestDocumentationContext() { + this(null); + } + + private RestDocumentationContext(Method testMethod) { + this.testMethod = testMethod; + } + + /** + * Returns the test {@link Method method} that is currently executing + * + * @return The test method + */ + public Method getTestMethod() { + return this.testMethod; + } + + /** + * Gets and then increments the current step count + * + * @return The step count prior to it being incremented + */ + int getAndIncrementStepCount() { + return this.stepCount.getAndIncrement(); + } + + /** + * Gets the current step count + * + * @return The current step count + */ + public int getStepCount() { + return this.stepCount.get(); + } + + static void establishContext(Method testMethod) { + CONTEXTS.set(new RestDocumentationContext(testMethod)); + } + + static void clearContext() { + CONTEXTS.set(null); + } + + /** + * Returns the current context, never {@code null}. + * + * @return The current context + */ + public static RestDocumentationContext currentContext() { + return CONTEXTS.get(); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationTestExecutionListener.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationTestExecutionListener.java new file mode 100644 index 00000000..b685699b --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationTestExecutionListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.config; + +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; + +/** + * A {@link TestExecutionListener} that sets up and tears down the Spring REST Docs + * context for each test method + * + * @author Andy Wilkinson + */ +public class RestDocumentationTestExecutionListener extends AbstractTestExecutionListener { + + @Override + public void beforeTestMethod(TestContext testContext) throws Exception { + RestDocumentationContext.establishContext(testContext.getTestMethod()); + } + + @Override + public void afterTestMethod(TestContext testContext) throws Exception { + RestDocumentationContext.clearContext(); + } +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/OutputFileResolver.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/OutputFileResolver.java new file mode 100644 index 00000000..86b9face --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/OutputFileResolver.java @@ -0,0 +1,101 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.snippet; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.restdocs.config.RestDocumentationContext; + +/** + * {@code OutputFileResolver} resolves an absolute output file based on the current + * configuration and context. + * + * @author Andy Wilkinson + */ +class OutputFileResolver { + + private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([A-Z])"); + + File resolve(String outputDirectory, String fileName) { + Map replacements = createReplacements(); + String path = outputDirectory; + for (Entry replacement : replacements.entrySet()) { + while (path.contains(replacement.getKey())) { + if (replacement.getValue() == null) { + throw new IllegalStateException("No replacement is available for " + + replacement.getKey()); + } + else { + path = path.replace(replacement.getKey(), replacement.getValue()); + } + } + } + + File outputFile = new File(path, fileName); + if (!outputFile.isAbsolute()) { + outputFile = makeRelativeToConfiguredOutputDir(outputFile); + } + return outputFile; + } + + private Map createReplacements() { + RestDocumentationContext context = RestDocumentationContext.currentContext(); + + Map replacements = new HashMap(); + replacements.put("{methodName}", context == null ? null : context.getTestMethod() + .getName()); + replacements.put("{method-name}", context == null ? null + : camelCaseToDash(context.getTestMethod().getName())); + replacements.put("{method_name}", context == null ? null + : camelCaseToUnderscore(context.getTestMethod().getName())); + replacements.put("{step}", + context == null ? null : Integer.toString(context.getStepCount())); + + return replacements; + } + + private String camelCaseToDash(String string) { + return camelCaseToSeparator(string, "-"); + } + + private String camelCaseToUnderscore(String string) { + return camelCaseToSeparator(string, "_"); + } + + private String camelCaseToSeparator(String string, String separator) { + Matcher matcher = CAMEL_CASE_PATTERN.matcher(string); + StringBuffer result = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(result, separator + matcher.group(1).toLowerCase()); + } + matcher.appendTail(result); + return result.toString(); + } + + private File makeRelativeToConfiguredOutputDir(File outputFile) { + File configuredOutputDir = new DocumentationProperties().getOutputDir(); + if (configuredOutputDir != null) { + return new File(configuredOutputDir, outputFile.getPath()); + } + return null; + } +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java index 4ae21075..5df71176 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java @@ -56,10 +56,8 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { } private Writer createWriter() throws IOException { - File outputFile = new File(this.outputDir, this.fileName + ".asciidoc"); - if (!outputFile.isAbsolute()) { - outputFile = makeRelativeToConfiguredOutputDir(outputFile); - } + File outputFile = new OutputFileResolver().resolve(this.outputDir, this.fileName + + ".asciidoc"); if (outputFile != null) { File parent = outputFile.getParentFile(); @@ -69,15 +67,9 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { } return new FileWriter(outputFile); } - - return new OutputStreamWriter(System.out); - } - - private File makeRelativeToConfiguredOutputDir(File outputFile) { - File configuredOutputDir = new DocumentationProperties().getOutputDir(); - if (configuredOutputDir != null) { - return new File(configuredOutputDir, outputFile.getPath()); + else { + return new OutputStreamWriter(System.out); } - return null; } + } \ No newline at end of file diff --git a/spring-restdocs/src/main/resources/META-INF/spring.factories b/spring-restdocs/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..64519d78 --- /dev/null +++ b/spring-restdocs/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.test.context.TestExecutionListener=org.springframework.restdocs.config.RestDocumentationTestExecutionListener \ No newline at end of file diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java new file mode 100644 index 00000000..7c485b44 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java @@ -0,0 +1,141 @@ +package org.springframework.restdocs; + +import static org.junit.Assert.assertTrue; +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationIntegrationTests.TestConfiguration; +import org.springframework.restdocs.config.RestDocumentationConfigurer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * Integration tests for Spring REST Docs + * + * @author Andy Wilkinson + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = TestConfiguration.class) +public class RestDocumentationIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Before + public void setOutputDirSystemProperty() { + System.setProperty("org.springframework.restdocs.outputDir", + "build/generated-snippets"); + } + + @Before + public void deleteSnippets() { + FileSystemUtils.deleteRecursively(new File("build/generated-snippets")); + } + + @After + public void clearOutputDirSystemProperty() { + System.clearProperty("org.springframework.restdocs.outputDir"); + } + + @Test + public void basicSnippetGeneration() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfigurer()).build(); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(document("basic")); + assertExpectedSnippetFilesExist(new File("build/generated-snippets/basic"), + "request.asciidoc", "response.asciidoc", "request-response.asciidoc"); + } + + @Test + public void parameterizedOutputDirectory() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfigurer()).build(); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(document("{method-name}")); + assertExpectedSnippetFilesExist(new File( + "build/generated-snippets/parameterized-output-directory"), + "request.asciidoc", "response.asciidoc", "request-response.asciidoc"); + } + + @Test + public void multiStep() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfigurer()) + .alwaysDo(document("{method-name}-{step}")).build(); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect( + status().isOk()); + assertExpectedSnippetFilesExist( + new File("build/generated-snippets/multi-step-1/"), "request.asciidoc", + "response.asciidoc", "request-response.asciidoc"); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect( + status().isOk()); + assertExpectedSnippetFilesExist( + new File("build/generated-snippets/multi-step-2/"), "request.asciidoc", + "response.asciidoc", "request-response.asciidoc"); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect( + status().isOk()); + assertExpectedSnippetFilesExist( + new File("build/generated-snippets/multi-step-3/"), "request.asciidoc", + "response.asciidoc", "request-response.asciidoc"); + + } + + private void assertExpectedSnippetFilesExist(File directory, String... snippets) { + for (String snippet : snippets) { + assertTrue(new File(directory, snippet).isFile()); + } + } + + @Configuration + @EnableWebMvc + static class TestConfiguration extends WebMvcConfigurerAdapter { + + @Bean + public TestController testController() { + return new TestController(); + } + + } + + @RestController + static class TestController { + + @RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) + public Map foo() { + Map response = new HashMap(); + response.put("a", "alpha"); + return response; + } + + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationConfigurerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java similarity index 95% rename from spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationConfigurerTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index 0b8d7e56..a4a93828 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationConfigurerTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.restdocs; +package org.springframework.restdocs.config; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.restdocs.RestDocumentationConfigurer; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.web.servlet.request.RequestPostProcessor; /** diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/OutputFileResolverTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/OutputFileResolverTests.java new file mode 100644 index 00000000..75755284 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/OutputFileResolverTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.snippet; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.lang.reflect.Method; + +import org.junit.Test; +import org.springframework.restdocs.config.RestDocumentationTestExecutionListener; +import org.springframework.test.context.TestContext; + +/** + * Tests for {@link OutputFileResolver}. + * + * @author Andy Wilkinson + */ +public class OutputFileResolverTests { + + private final OutputFileResolver resolver = new OutputFileResolver(); + + @Test + public void noConfiguredOutputDirectoryAndRelativeInput() { + assertThat(this.resolver.resolve("foo", "bar.txt"), is(nullValue())); + } + + @Test + public void absoluteInput() { + String absolutePath = new File("foo").getAbsolutePath(); + assertThat(this.resolver.resolve(absolutePath, "bar.txt"), is(new File( + absolutePath, "bar.txt"))); + } + + @Test + public void configuredOutputAndRelativeInput() { + String outputDir = new File("foo").getAbsolutePath(); + System.setProperty("org.springframework.restdocs.outputDir", outputDir); + try { + assertThat(this.resolver.resolve("bar", "baz.txt"), is(new File(outputDir, + "bar/baz.txt"))); + } + finally { + System.clearProperty("org.springframework.restdocs.outputDir"); + } + } + + @Test + public void configuredOutputAndAbsoluteInput() { + String outputDir = new File("foo").getAbsolutePath(); + String absolutePath = new File("bar").getAbsolutePath(); + System.setProperty("org.springframework.restdocs.outputDir", outputDir); + try { + assertThat(this.resolver.resolve(absolutePath, "baz.txt"), is(new File( + absolutePath, "baz.txt"))); + } + finally { + System.clearProperty("org.springframework.restdocs.outputDir"); + } + } + + @Test(expected = IllegalStateException.class) + public void placeholderWithoutAReplacement() { + this.resolver.resolve("{method-name}", "foo.txt"); + } + + @Test + public void dashSeparatedMethodName() throws Exception { + RestDocumentationTestExecutionListener listener = new RestDocumentationTestExecutionListener(); + TestContext testContext = mock(TestContext.class); + Method method = getClass().getMethod("dashSeparatedMethodName"); + when(testContext.getTestMethod()).thenReturn(method); + listener.beforeTestMethod(testContext); + try { + assertThat(this.resolver.resolve(new File("{method-name}").getAbsolutePath(), + "foo.txt"), + is(new File(new File("dash-separated-method-name").getAbsolutePath(), + "foo.txt"))); + } + finally { + listener.afterTestMethod(testContext); + } + } + + @Test + public void underscoreSeparatedMethodName() throws Exception { + RestDocumentationTestExecutionListener listener = new RestDocumentationTestExecutionListener(); + TestContext testContext = mock(TestContext.class); + Method method = getClass().getMethod("underscoreSeparatedMethodName"); + when(testContext.getTestMethod()).thenReturn(method); + listener.beforeTestMethod(testContext); + try { + assertThat( + this.resolver.resolve(new File("{method_name}").getAbsolutePath(), + "foo.txt"), + is(new File(new File("underscore_separated_method_name") + .getAbsolutePath(), "foo.txt"))); + } + finally { + listener.afterTestMethod(testContext); + } + } + + @Test + public void camelCaseMethodName() throws Exception { + RestDocumentationTestExecutionListener listener = new RestDocumentationTestExecutionListener(); + TestContext testContext = mock(TestContext.class); + Method method = getClass().getMethod("camelCaseMethodName"); + when(testContext.getTestMethod()).thenReturn(method); + listener.beforeTestMethod(testContext); + try { + assertThat(this.resolver.resolve(new File("{methodName}").getAbsolutePath(), + "foo.txt"), + is(new File(new File("camelCaseMethodName").getAbsolutePath(), + "foo.txt"))); + } + finally { + listener.afterTestMethod(testContext); + } + } + + @Test + public void stepCount() throws Exception { + RestDocumentationTestExecutionListener listener = new RestDocumentationTestExecutionListener(); + TestContext testContext = mock(TestContext.class); + Method method = getClass().getMethod("stepCount"); + when(testContext.getTestMethod()).thenReturn(method); + listener.beforeTestMethod(testContext); + try { + assertThat(this.resolver.resolve(new File("{step}").getAbsolutePath(), + "foo.txt"), is(new File(new File("0").getAbsolutePath(), "foo.txt"))); + } + finally { + listener.afterTestMethod(testContext); + } + } +}