spring-projects / spring-restdocs

Test-driven documentation for RESTful services
https://spring.io/projects/spring-restdocs
Apache License 2.0
1.16k stars 735 forks source link

Consistency with the subsection path. #937

Open ddaaac opened 3 months ago

ddaaac commented 3 months ago

Hi, I have two JSON string.

{
  "a": {
    "b": [
      {
        "c": "c1"
      }
    ]
  }
}
{
  "a": [
    {
      "b": [
        {
          "c": "c1"
        }
      ]
    }
  ]
}

First one, A(B(List)) responseFields(fieldWithPath("a.b[].c")) <- Test passed. responseFields(beneathPath("a.b[]"), listOf(fieldWithPath("c"))) <- Test passed. responseFields(beneathPath("a.b[]"), listOf(fieldWithPath("[].c"))) <- Test failed.

Second one, A(List<B(List)>) responseFields(fieldWithPath("a[].b[].c")) <- Test passed. responseFields(beneathPath("a[].b[]"), listOf(fieldWithPath("c"))) <- Test failed. responseFields(beneathPath("a[].b[]"), listOf(fieldWithPath("[].c"))) <- Test passed.

Is this intentional? Does the way to find the path change if there is an array in the root?


Additional information.

responseFields(fieldWithPath("a.b[].c[].d")) <- Test passed. responseFields(beneathPath("a.b[].c[]"), listOf(fieldWithPath("d"))) <- Test failed. responseFields(beneathPath("a.b[].c[]"), listOf(fieldWithPath("[].d"))) <- Test Passed.

responseFields(fieldWithPath("a.b[].c.d")) <- Test passed. responseFields(beneathPath("a.b[].c"), listOf(fieldWithPath("d"))) <- Test Passed. responseFields(beneathPath("a.b[].c"), listOf(fieldWithPath("[].d"))) <- Test failed.

I think an array of 1 depth behaves differently from an array of n depth(n >= 2).

wilkinsona commented 2 months ago

See also https://github.com/spring-projects/spring-restdocs/issues/627.

wilkinsona commented 2 months ago

@ddaaac could you please share your complete tests? I'd like to be able to run them to see exactly what's happening with the field paths.

ddaaac commented 2 months ago
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.restdocs.RestDocumentationExtension
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get
import org.springframework.restdocs.payload.JsonFieldType
import org.springframework.restdocs.payload.PayloadDocumentation.*
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
@ExtendWith(RestDocumentationExtension::class)
class ArrayTest {

    @Autowired
    private lateinit var mockMvc: MockMvc

    @Test
    fun `test response1`() {
        mockMvc.perform(get("/response1"))
            .andDo(
                document(
                    "response1",
                    responseFields(
                        fieldWithPath("a.b[].c").type(JsonFieldType.STRING).description("root")
                    ),
                    responseFields(
                        beneathPath("a.b[]").withSubsectionId("array"),
                        fieldWithPath("c").type(JsonFieldType.STRING).description("c")
                    )
//                    responseFields(
//                        beneathPath("a.b[]").withSubsectionId("nestedArray"),
//                        fieldWithPath("[].c").type(JsonFieldType.STRING).description("c")
//                    )
                )
            )
    }

    @Test
    fun `test response2`() {
        mockMvc.perform(get("/response2"))
            .andExpect(status().isOk)
            .andDo(
                document(
                    "response2",
                    responseFields(
                        fieldWithPath("a[].b[].c").type(JsonFieldType.STRING).description("root")
                    ),
//                    responseFields(
//                        beneathPath("a[].b[]").withSubsectionId("array"),
//                        fieldWithPath("c").type(JsonFieldType.STRING).description("c")
//                    )
                    responseFields(
                        beneathPath("a[].b[]").withSubsectionId("nestedArray"),
                        fieldWithPath("[].c").type(JsonFieldType.STRING).description("c")
                    )
                )
            )
    }

}

@RestController
class MyController {

    @GetMapping("/response1")
    fun getResponse1(): Map<String, Any> {
        return mapOf(
            "a" to mapOf(
                "b" to listOf(
                    mapOf(
                        "c" to "c1"
                    )
                )
            )
        )
    }

    @GetMapping("/response2")
    fun getResponse2(): Map<String, Any> {
        return mapOf(
            "a" to listOf(
                mapOf(
                    "b" to listOf(
                        mapOf(
                            "c" to "c1"
                        )
                    )
                )
            )
        )
    }
}

I use kotlin. The commented part is the part that fails.

wilkinsona commented 2 months ago

Thanks.