使用 Spring REST Docs 文档分层 JSON 负载

document hierarchical JSON payload with Spring REST Docs

我开始使用 Spring REST Docs 来记录一个简单的 REST API。我有一个具有某种层次结构的有效负载,例如像这样(有员工的公司)。

{
    "companyName": "FooBar",
    "employee": 
    [
        {
            "name": "Lorem",
            "age": "42"
        },

        {
            "name": "Ipsum",
            "age": "24"
        }
    ]
}

我想将公司对象(员工姓名和数组)和员工对象(员工姓名和年龄)的文档分开。

here 解释的那样使用 org.springframework.restdocs.payload.PayloadDocumentation.responseFields 迫使我记录所有字段,但万一我只想记录 employee 字段 - 我怎么能实现这个?

我可以在没有员工详细信息的情况下记录公司,因为如果一个字段是文档,则后代也被视为已记录。但是我无法单独记录员工结构,没有公司根对象,我没有专门用于此结构的有效负载。

受此问题的启发,我实施了一项增强功能,使原始答案(见下文)过时。

如果您使用 1.0.0.BUILD-SNAPSHOT(可从 https://repo.spring.io/libs-snapshot 获得),您现在可以将字段标记为已忽略。已记录忽略的字段数,但并未实际出现在文档中。

鉴于您想要分离文档,进行两次文档调用是有意义的。在第一个文件中,您可以记录公司名称和员工数组。在第二个文件中,您记录了员工数组并将公司名称标记为已忽略。

你的测试看起来像这样:

mockMvc.perform(get("/company/5").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andDo(document("company",
                responseFields(
                        fieldWithPath("companyName").description(
                                "The name of the company"),
                        fieldWithPath("employee").description(
                                "An array of the company's employees"))))
        .andDo(document("employee",
                responseFields(
                        fieldWithPath("companyName").ignored(),
                        fieldWithPath("employee[].name").description(
                                "The name of the employee"),
                        fieldWithPath("employee[].age").description(
                                "The age of the employee"))));

您最终会得到两个片段目录,一个名为 company,一个名为 employee。然后,您可以使用每个片段中的 response-fields.adoc 片段。

原回答

没有明确支持在记录请求或响应时忽略字段,但我认为您可以通过使用预处理器删除不想记录的字段来实现您想要的效果.

鉴于您想要分离文档,进行两次 document 调用是有意义的。在第一个文件中,您可以记录公司名称和员工数组。在第二个中,您需要预处理删除公司的请求,然后记录员工数组。

你的测试看起来像这样:

mockMvc.perform(get("/company/5").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andDo(document("company",
                responseFields(
                        fieldWithPath("companyName").description(
                                "The name of the company"),
                        fieldWithPath("employee").description(
                                "An array of the company's employees"))))
        .andDo(document("employee",
                preprocessResponse(removeCompany()),
                responseFields(
                        fieldWithPath("employee[].name").description(
                                "The name of the employee"),
                        fieldWithPath("employee[].age").description(
                                "The age of the employee"))));

注意在第二个 document 调用中使用 preprocessResponseremoveCompany returns 使用自定义 ContentModifier 从响应中删除公司名称的预处理器:

private OperationPreprocessor removeCompany() {
    return new ContentModifyingOperationPreprocessor(new ContentModifier() {

        @Override
        public byte[] modifyContent(byte[] originalContent, MediaType contentType) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                Map<?, ?> map = objectMapper.readValue(originalContent, Map.class);
                map.remove("companyName");
                return objectMapper.writeValueAsBytes(map);
            }
            catch (IOException ex) {
                return originalContent;
            }
        }

    });
}

您最终会得到两个片段目录,一个名为 company,一个名为 employee。然后,您可以使用每个片段中的 response-fields.adoc 片段。

虽然上面的方法可行,但比需要的要难。我已经打开 an issue 这样就不再需要修改响应内容的预处理了。