๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring

[Spring] REST Docs

by Bhinney 2023. 1. 4.

๐Ÿ”ŽREST Docs๋ž€?

  • Test ์ฝ”๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ Restful API ์„œ๋น„์Šค๋ฅผ ๋ฌธ์„œํ™”๋ฅผ ๋„์›€
  • Asciidoctor๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ด๋ฅผ ์ด์šฉํ•ด html์„ ์ƒ์„ฑ
  • ํฐ ์žฅ์ ์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋Š” ์ 
  • ํ…Œ์ŠคํŠธ ํ†ต๊ณผ์‹œ Snippet์ด ์ƒ์„ฑ๋จ
  • ๋งŒ์•ฝ ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ, Snippet์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ

๐Ÿ’กREST Docs VS Swagger

  REST Docs Swagger
์žฅ์  ํ…Œ์ŠคํŠธ์— ์„ฑ๊ณตํ•ด์•ผ ๋ฌธ์„œ ์ž‘์„ฑ ๊ฐ€๋Šฅ API ๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด ๋ณผ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์„ ์ œ๊ณต
์ œํ’ˆ ์ฝ”๋“œ์— ์˜ํ–ฅ์ด ์—†์Œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ํ•„์š” ์—†์œผ๋ฏ€๋กœ ์ ์šฉํ•˜๊ธฐ ์‰ฌ์›€
๋‹จ์  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ ์šฉ์ด ๋ถˆํŽธ ์ œํ’ˆ ์ฝ”๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ 
ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— 100% ์ •ํ™•ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ
  • Swagger ์‚ฌ์šฉ ์‹œ, ์ œํ’ˆ ์ฝ”๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
  • ๋•Œ๋ฌธ์— ์ง€์†์ ์œผ๋กœ ์‚ฌ์šฉ ์‹œ, ์–ด๋…ธํ…Œ์ด์…˜์ด ๋งŽ์ด ๋ถ™๊ฒŒ ๋˜๊ณ  ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Œ

๐Ÿ”Ž ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— : build.gradle ์ถ”๊ฐ€

: REST Docs๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, ํ•„์š”ํ•œ ๋ถ€๋ถ„๋“ค์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์—ˆ๋‹ค.

๋”๋ณด๊ธฐ
plugins {
   ...
   
   id "org.asciidoctor.jvm.convert" version "3.3.2" /*  ์ถ”๊ฐ€ */
}

group = 'org.study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
   ...
   
   asciidoctorExtensions /*  ์ถ”๊ฐ€ */
}

repositories {
   mavenCentral()
}

ext {
   set('snippetsDir', file("build/generated-snippets")) /*  ์ถ”๊ฐ€ */
}

dependencies {
   
   ...

   /* Rest Docs ์ถ”๊ฐ€ */
   testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
   asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

   /* Gson */
   implementation 'com.google.code.gson:gson'
}

/* ์•„๋ž˜์˜ ๋‚ด์šฉ๋“ค ์ถ”๊ฐ€ */

tasks.named('test') {
   outputs.dir snippetsDir
   useJUnitPlatform()
}

tasks.named('asciidoctor') {
   configurations "asciidoctorExtensions"
   inputs.dir snippetsDir
   dependsOn test
}

task copyDocument(type: Copy) {
   dependsOn asciidoctor
   from file("${asciidoctor.outputDir}")
   into file("src/main/resources/static/docs")
}

build {
   dependsOn copyDocument
}

bootJar {
   dependsOn copyDocument
   from("${asciidoctor.outputDir}") {
      into 'static/docs'

   }
}

๐Ÿ”Ž REST Docs ์ƒ์„ฑ 1 - Test Code ์ž‘์„ฑ

 

GitHub - Bhinney/Rec_Study: โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ

โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ. Contribute to Bhinney/Rec_Study development by creating an account on GitHub.

github.com

1๏ธโƒฃ ์š”์ฒญ๊ณผ ์‘๋‹ต ์˜ˆ์˜๊ฒŒ ํ”„๋ฆฐํŠธ ํ•˜๊ธฐ ์œ„ํ•œ Util ์ถ”๊ฐ€

๋”๋ณด๊ธฐ
public interface ApiDocumentUtils {
	static OperationRequestPreprocessor getRequestPreProcessor() {
		return preprocessRequest(prettyPrint());
	}

	static OperationResponsePreprocessor getResponsePreProcessor() {
		return preprocessResponse(prettyPrint());
	}
}

 

2๏ธโƒฃ BoardControllerTest ํด๋ž˜์Šค ์ƒ์„ฑ

: ๊ฒŒ์‹œ๋ฌผ ๋“ฑ๋ก๊ณผ ์ˆ˜์ •์€ StubData๊ฐ€ ์•„๋‹Œ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์—ˆ๋‹ค.

๋”๋ณด๊ธฐ
@WebMvcTest(BoardController.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
public class BoardControllerTest {

   @Autowired
   private MockMvc mockMvc;

   @MockBean
   private BoardService boardService;

   @MockBean
   private BoardMapper mapper;

   @Autowired
   private Gson gson;

   @Test
   @DisplayName("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ํ…Œ์ŠคํŠธ")
   public void postBoardTest() throws Exception {

      /* given */
      BoardDto.Post post = new BoardDto.Post("๊น€์ด๋ฆ„", "์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.");
      String content = gson.toJson(post);

      BoardDto.Response response =
         new BoardDto.Response(1L, "๊น€์ด๋ฆ„", "์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.", LocalDate.now(), LocalDate.now());

      given(mapper.boardPostDtoToBoard(Mockito.any(BoardDto.Post.class))).willReturn(new Board());
      given(boardService.create(Mockito.any(Board.class))).willReturn(new Board());
      given(mapper.boardToBoardResponseDto(Mockito.any(Board.class))).willReturn(response);

      /* when */
      ResultActions actions
         = mockMvc.perform(
         post("/api/boards")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .content(content)
      );

      /* then */
      actions
         .andExpect(status().isCreated())
         .andExpect(jsonPath("$.nickName").value(post.getNickName()))
         .andExpect(jsonPath("$.title").value(post.getTitle()))
         .andExpect(jsonPath("$.content").value(post.getContent()))
         .andDo(document("post-board",
            getRequestPreProcessor(),
            getResponsePreProcessor(),
            requestFields(
               List.of(
                  fieldWithPath("nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
                  fieldWithPath("title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
                  fieldWithPath("content").type(JsonFieldType.STRING).description("๋‚ด์šฉ")
               )
            ),
            responseFields(
               List.of(
                  fieldWithPath("boardId").type(JsonFieldType.NUMBER).description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž"),
                  fieldWithPath("nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
                  fieldWithPath("title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
                  fieldWithPath("content").type(JsonFieldType.STRING).description("๋‚ด์šฉ"),
                  fieldWithPath("createdAt").type(JsonFieldType.STRING).description("์ตœ์ดˆ ์ž‘์„ฑ ์ผ์ž"),
                  fieldWithPath("modifiedAt").type(JsonFieldType.STRING).description("์ˆ˜์ • ์ผ์ž")
               )
            )
         ));
   }

   @Test
   @DisplayName("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ํ…Œ์ŠคํŠธ")
   public void patchBoardTest() throws Exception {

      /* given */
      long boardId = 1L;
      BoardDto.Patch patch = new BoardDto.Patch("์ˆ˜์ •๋œ ์ด๋ฆ„", "์ˆ˜์ •๋œ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "์ˆ˜์ •๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.");
      String content = gson.toJson(patch);

      BoardDto.Response response =
         new BoardDto.Response(1L,"์ˆ˜์ •๋œ ์ด๋ฆ„", "์ˆ˜์ •๋œ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "์ˆ˜์ •๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.", LocalDate.now(), LocalDate.now());

      given(mapper.boardPatchDtoToBoard(Mockito.any(BoardDto.Patch.class))).willReturn(new Board());
      given(boardService.update(Mockito.any(Board.class), Mockito.anyLong())).willReturn(new Board());
      given(mapper.boardToBoardResponseDto(Mockito.any(Board.class))).willReturn(response);

      /* when */
      ResultActions actions
         = mockMvc.perform(
         patch("/api/boards/{boardId}", boardId)
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .content(content)
      );

      /* then */
      actions
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.nickName").value(patch.getNickName()))
         .andExpect(jsonPath("$.title").value(patch.getTitle()))
         .andExpect(jsonPath("$.content").value(patch.getContent()))
         .andDo(document("patch-board",
            getRequestPreProcessor(),
            getResponsePreProcessor(),
            requestFields(
               List.of(
                  fieldWithPath("nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
                  fieldWithPath("title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
                  fieldWithPath("content").type(JsonFieldType.STRING).description("๋‚ด์šฉ")
               )
            ),
            responseFields(
               List.of(
                  fieldWithPath("boardId").type(JsonFieldType.NUMBER).description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž"),
                  fieldWithPath("nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
                  fieldWithPath("title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
                  fieldWithPath("content").type(JsonFieldType.STRING).description("๋‚ด์šฉ"),
                  fieldWithPath("createdAt").type(JsonFieldType.STRING).description("์ตœ์ดˆ ์ž‘์„ฑ ์ผ์ž"),
                  fieldWithPath("modifiedAt").type(JsonFieldType.STRING).description("์ˆ˜์ • ์ผ์ž")
               )
            )
         ));
   }

}

 

3๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด StubData ์ƒ์„ฑ

: ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ StubData๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

๋•Œ๋ฌธ์— ์ดํ›„์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ˆ˜์ •์„ ํ•ด๋ณผ ์˜ˆ์ •์ด๋‹ค.

๋”๋ณด๊ธฐ
public class StubData {
	private static Map<HttpMethod, Object> stubRequestBody;
	static {
		stubRequestBody = new HashMap<>();
		stubRequestBody.put(HttpMethod.POST, new BoardDto.Post("๊น€์ด๋ฆ„", "์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "๋‚ด์šฉ์ž…๋‹ˆ๋‹ค."));
		stubRequestBody.put(HttpMethod.PATCH, new BoardDto.Patch("์ˆ˜์ •๋œ ์ด๋ฆ„", "์ˆ˜์ •๋œ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.", "์ˆ˜์ •๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค."));
	}

	public static class MockBoard {
		public static Object getRequestBody(HttpMethod method) {
			return stubRequestBody.get(method);
		}

		public static BoardDto.Response getBoardResponse() {
			BoardDto.Response board = BoardDto.Response.builder()
				.boardId(1L)
				.nickName("๊น€์ด๋ฆ„")
				.title("์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
				.content("๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
				.createdAt(LocalDate.now())
				.modifiedAt(LocalDate.now())
				.build();

			return board;
		}

		public static Page<Board> getBoardListStubData() {
			Board board1 = Board.builder()
					.nickName("๊น€์ด๋ฆ„1")
					.title("์ฒซ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
					.content("์ฒซ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
					.build();

			Board board2 = Board.builder()
					.nickName("๊น€์ด๋ฆ„2")
					.title("๋‘ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
					.content("๋‘ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
					.build();

			Board board3 = Board.builder()
					.nickName("๊น€์ด๋ฆ„3")
					.title("์„ธ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
					.content("์„ธ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
					.build();

			return new PageImpl<>(List.of(board3, board2, board1),
				PageRequest.of(0, 10, Sort.by("memberId").descending()),3);
		}

		public static List<BoardDto.Response> getBoardListResponse() {
			BoardDto.Response board1 = BoardDto.Response.builder()
				.boardId(1L)
				.nickName("๊น€์ด๋ฆ„1")
				.title("์ฒซ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
				.content("์ฒซ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
				.createdAt(LocalDate.now())
				.modifiedAt(LocalDate.now())
				.build();

			BoardDto.Response board2 = BoardDto.Response.builder()
				.boardId(2L)
				.nickName("๊น€์ด๋ฆ„2")
				.title("๋‘ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
				.content("๋‘ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
				.createdAt(LocalDate.now())
				.modifiedAt(LocalDate.now())
				.build();

			BoardDto.Response board3 = BoardDto.Response.builder()
				.boardId(3L)
				.nickName("๊น€์ด๋ฆ„3")
				.title("์„ธ ๋ฒˆ์งธ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
				.content("์„ธ ๋ฒˆ์งธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
				.createdAt(LocalDate.now())
				.modifiedAt(LocalDate.now())
				.build();

			return List.of(
				board3, board2, board1
			);
		}

		public static Board getBoardStubData(long boardId) {
			Board board = Board.builder()
					.nickName("๊น€์ด๋ฆ„")
					.title("์ œ๋ชฉ์ž…๋‹ˆ๋‹ค.")
					.content("๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.")
					.build();

			return board;
		}
	}
}

 

4๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด Helper ์ž‘์„ฑ

: ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ResultActions์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ Helper ์ž‘์„ฑ 

๋”๋ณด๊ธฐ
public interface ControllerTestHelper<T> {

	default RequestBuilder getRequestBuilder(String url, long sourceId) {
		return get(url, sourceId)
			.accept(MediaType.APPLICATION_JSON);
	}

	default RequestBuilder getRequestBuilder(String url, MultiValueMap<String, String> queryParams){
		return get(url)
			.params(queryParams)
			.accept(MediaType.APPLICATION_JSON);
	}


	default RequestBuilder deleteRequestBuilder(String uri, long sourceId) {
		return delete(uri, sourceId);
	}

	default URI createURI(String url) {
		return UriComponentsBuilder.newInstance().path(url).build().toUri();
	}

	default URI createURI(String url, long resourceId) {
		return UriComponentsBuilder.newInstance().path(url).buildAndExpand(resourceId).toUri();
	}

	default String toJsonContent(T t) {
		Gson gson = new Gson();
		String content = gson.toJson(t);
		return content;
	}
}

 

public interface BoardControllerHelper extends ControllerTestHelper {
	String url = "/api/boards";
	String resource = "/{boardId}";

	default String getBoardURI() {
		return url;
	}


	default String getBoardResourceURI() {
		return url + resource;
	}
}

 

4๏ธโƒฃ BoardControllerTest ํด๋ž˜์Šค ์ถ”๊ฐ€ ๊ตฌํ˜„

: ํŠน์ • ๊ฒŒ์‹œ๋ฌผ ์กฐํšŒ, ๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ, ๊ฒŒ์‹œํŒ ์‚ญ์ œ์— ๋Œ€ํ•œ ํ…Œ์Šคํ„ฐ๋Š” StubData์™€ Helper๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

Helpers๋Š” implements ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

๋”๋ณด๊ธฐ
@WebMvcTest(BoardController.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
public class BoardControllerTest implements BoardControllerHelper {

	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private BoardService boardService;

	@MockBean
	private BoardMapper mapper;

	@Autowired
	private Gson gson;
	
    ...

	@Test
	@DisplayName("ํŠน์ • ๊ฒŒ์‹œ๋ฌผ ์กฐํšŒ ํ…Œ์ŠคํŠธ")
	public void getBoardTest() throws Exception {
		/* given */
		long boardId = 1L;
		BoardDto.Response response = StubData.MockBoard.getBoardResponse();

		String url = getBoardResourceURI();

		given(boardService.findBoard(Mockito.anyLong())).willReturn(new Board());
		given(mapper.boardToBoardResponseDto(Mockito.any(Board.class))).willReturn(response);

		/* when */
		ResultActions actions = mockMvc.perform(getRequestBuilder(url, boardId));

		/* then */
		actions.andExpect(status().isOk())
			.andDo(document("get-board",
				getResponsePreProcessor(),
				pathParameters(parameterWithName("boardId").description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž")),
				responseFields(
					List.of(
						fieldWithPath("boardId").type(JsonFieldType.NUMBER).description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž"),
						fieldWithPath("nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
						fieldWithPath("title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
						fieldWithPath("content").type(JsonFieldType.STRING).description("๋‚ด์šฉ"),
						fieldWithPath("createdAt").type(JsonFieldType.STRING).description("์ตœ์ดˆ ์ž‘์„ฑ ์ผ์ž"),
						fieldWithPath("modifiedAt").type(JsonFieldType.STRING).description("์ˆ˜์ • ์ผ์ž")
					)
				)
			));
	}

	@Test
	@DisplayName("๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ")
	public void getBoardListTest() throws Exception {
		Page<Board> boardList = StubData.MockBoard.getBoardListStubData();
		List<BoardDto.Response> responseList = StubData.MockBoard.getBoardListResponse();

		String url = getBoardURI();

		MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
		queryParams.add("page", "1");
		queryParams.add("size", "3");

		given(boardService.findBoardList(Mockito.anyInt(), Mockito.anyInt())).willReturn(boardList);
		given(mapper.boardToBoardResponseDto(Mockito.anyList())).willReturn(responseList);

		/* when */
		ResultActions actions = mockMvc.perform(getRequestBuilder(url,queryParams));

		/* then */
		actions.andExpect(status().isOk())
			.andDo(document("get-boardList",
				getRequestPreProcessor(),
				getResponsePreProcessor(),
				requestParameters(
					List.of(
						parameterWithName("page").description("ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ"),
						parameterWithName("size").description("ํŽ˜์ด์ง€ ์‚ฌ์ด์ฆˆ")
					)
				),
				responseFields(
					List.of(
						fieldWithPath("[].boardId").type(JsonFieldType.NUMBER).description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž"),
						fieldWithPath("[].nickName").type(JsonFieldType.STRING).description("์ž‘์„ฑ์ž"),
						fieldWithPath("[].title").type(JsonFieldType.STRING).description("์ œ๋ชฉ"),
						fieldWithPath("[].content").type(JsonFieldType.STRING).description("๋‚ด์šฉ"),
						fieldWithPath("[].createdAt").type(JsonFieldType.STRING).description("์ตœ์ดˆ ์ž‘์„ฑ ์ผ์ž"),
						fieldWithPath("[].modifiedAt").type(JsonFieldType.STRING).description("์ˆ˜์ • ์ผ์ž")
					)
				)
			));
	}

	@Test
	@DisplayName("๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ํ…Œ์ŠคํŠธ")
	public void deleteBoardTest() throws Exception {
		long boardId = 1L;
		String message = "๊ฒŒ์‹œํŒ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
		given(boardService.delete(Mockito.anyLong())).willReturn(message);

		String url = getBoardResourceURI();

		/* when */
		ResultActions actions = mockMvc.perform(deleteRequestBuilder(url, boardId));

		actions.andExpect(status().isOk())
			.andDo(document("delete-board",
					pathParameters(parameterWithName("boardId").description("๊ฒŒ์‹œํŒ ์‹๋ณ„์ž"))
				)
			);
	}
}

๐Ÿ”Ž REST Docs ์ƒ์„ฑ 2 - adoc ๋ฌธ์„œ ์ž‘์„ฑ & Html ์ƒ์„ฑ

1๏ธโƒฃ Test๋ฅผ ํ•˜์—ฌ ํ†ต๊ณผ๊ฐ€ ๋˜๋ฉด build ํด๋” ์•ˆ์— Snippet์ด ์ƒ์„ฑ ๋จ

 

2๏ธโƒฃ ํ•ด๋‹น ์Šค๋‹ˆํ•์„ ๋ฐ”ํƒ•์œผ๋กœ adoc ๋ฌธ์„œ ์ž‘์„ฑ

 

GitHub - Bhinney/Rec_Study: โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ

โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ. Contribute to Bhinney/Rec_Study development by creating an account on GitHub.

github.com

๋”๋ณด๊ธฐ
= ๊ฒŒ์‹œํŒ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜

:sectnums:
:toc: left
:toclevels: 4
:toc-title: Table of Contents
:source-highlighter: prettify


v1.0.0, 2023.01.04

***

 ์ด ๋ฌธ์„œ๋Š” ์Šคํ”„๋ง ๊ณต๋ถ€๋ฅผ ์œ„ํ•ด ์ž‘์„ฑ๋œ ๊ฒŒ์‹œํŒ CRUD์˜ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.

***
== BoardController
==== ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก
.curl-request
include::{snippets}/post-board/curl-request.adoc[]

.http-request
include::{snippets}/post-board/http-request.adoc[]

.request-fields
include::{snippets}/post-board/request-fields.adoc[]

.http-response
include::{snippets}/post-board/http-response.adoc[]

.response-fields
include::{snippets}/post-board/response-fields.adoc[]

=== ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •
.curl-request
include::{snippets}/patch-board/curl-request.adoc[]

.http-request
include::{snippets}/patch-board/http-request.adoc[]

.request-fields
include::{snippets}/patch-board/request-fields.adoc[]

.http-response
include::{snippets}/patch-board/http-response.adoc[]

.response-fields
include::{snippets}/patch-board/response-fields.adoc[]


=== ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ

.curl-request
include::{snippets}/get-board/curl-request.adoc[]

.http-request
include::{snippets}/get-board/http-request.adoc[]

.http-response
include::{snippets}/get-board/http-response.adoc[]

.response-fields
include::{snippets}/get-board/response-fields.adoc[]


=== ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ
.curl-request
include::{snippets}/get-boardList/curl-request.adoc[]

.http-request
include::{snippets}/get-boardList/http-request.adoc[]

.request-parameters
include::{snippets}/get-boardList/request-parameters.adoc[]

.http-response
include::{snippets}/get-boardList/http-response.adoc[]

.response-fields
include::{snippets}/get-boardList/response-fields.adoc[]

=== ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ
.curl-request
include::{snippets}/delete-board/curl-request.adoc[]

.http-request
include::{snippets}/delete-board/http-request.adoc[]

.http-response
include::{snippets}/delete-board/http-response.adoc[]

 

3๏ธโƒฃ Gradle ์—์„œ bootJar ์‹คํ–‰

 

4๏ธโƒฃ ์ œ๋Œ€๋กœ ์‹คํ–‰์ด ๋˜์—ˆ๋‹ค๋ฉด resources/static/docs์•ˆ์— html์ด ์ƒ์„ฑ

 

GitHub - Bhinney/Rec_Study: โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ

โœจ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•˜๋Š” ๊ณต๊ฐ„ โœจ. Contribute to Bhinney/Rec_Study development by creating an account on GitHub.

github.com

 

5๏ธโƒฃ ํ•ด๋‹น html ํŒŒ์ผ์„ ํฌ๋กฌ์œผ๋กœ ์—ด์–ด ๋ณด๊ธฐ

: ์•„๋ž˜์˜ ์ผ๋ถ€๋ฅผ ์บก์ณํ•ด ์™”๋‹ค. ์•„๋ž˜์˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.


๋Œ“๊ธ€