【问题标题】:Testing Form posts through MockMVC通过 MockMVC 测试表单帖子
【发布时间】:2016-08-02 18:50:50
【问题描述】:

我正在编写测试以验证我是否可以向我们的 API 发布通用表单。

我还添加了一些调试,但我注意到实际表单发布的数据; (Postman / AngularJS 或 w/e)不同于进行 mockMVC 测试,例如:

MvcResult response = mockMvc
            .perform(post("/some/super/secret/url") //
                    .param("someparam1", "somevalue") //
                    .param("someparam2", "somevalue") //                
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED) //
                    .accept(MediaType.APPLICATION_JSON)) //
            .andExpect(status().isOk()) //
            .andReturn();

配置与生产中运行的配置完全相同,等等。但是,当我的拦截器记录内容时,在实际测试(不是 mockMVC)中,内容的格式类似于“someparam1=somevalue&etc=encore”

当我打印 mockMVC 内容时,我实际上似乎没有内容,但请求中有 Params,我假设它们像 GET 参数一样添加。

有人知道如何正确测试吗?我遇到了这个问题,因为看起来我们的表单帖子似乎没有被 Spring 解析,即使我们已将 FormHttpMessageConverter 添加到 servlet 上下文中。

【问题讨论】:

  • 您能否提供更多详细信息,例如配置、您发布到服务器的真实数据以及一些日志详细信息以使其更清晰?关于模拟,看来您做得对,模拟测试的参数与您显示的发布数据格式相同。另外,请检查您是否有类似弹簧安全的想法。在执行后续步骤之前,您可能需要模拟 spring 安全性或执行登录。
  • 我们不使用类似的东西,我发现我的例子实际上是错误的。在这种情况下 someParam1 和 someParam2 将是一个查询参数,(在 url 中)。我想使用表单参数因此 URLENCODED。然而,这似乎只能通过模型​​或值图来访问。所以我们需要一个解决方法(这被迁移为完全 spring mvc,以前是 @FormParam 而不是 spring)。
  • 哦,还有发布的数据,就像我在 Mvc 部分下方的示例中一样,它发布了一个与 & 和 = 连接的内容字符串
  • 由于缺乏细节,我不知道您如何处理控制器中的发布数据。但是根据申请表的 url 编码,您发布的参数是正确的格式。这里的参数并不意味着只能在url中,它也可以在post内容中,格式相同&name=value
  • 在 Spring Boot 2.2.6 上,同样的设置对我来说效果很好。所以,我认为他们在此期间已经解决了这个问题。

标签: spring form-data


【解决方案1】:

如果你的类路径上有 Apache HTTPComponents HttpClient,你可以这样做:

    mockMvc.perform(post("/some/super/secret/url")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .content(EntityUtils.toString(new UrlEncodedFormEntity(Arrays.asList(
                    new BasicNameValuePair("someparam1", "true"),
                    new BasicNameValuePair("someparam2", "test")
            )))));

如果你没有 HttpClient,你可以用一个简单的帮助方法来构造 urlencoded 表单实体:

    mockMvc.perform(post("/some/super/secret/url")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .content(buildUrlEncodedFormEntity(
         "someparam1", "value1", 
         "someparam2", "value2"
    ))));

使用这个辅助函数:

private String buildUrlEncodedFormEntity(String... params) {
    if( (params.length % 2) > 0 ) {
       throw new IllegalArgumentException("Need to give an even number of parameters");
    }
    StringBuilder result = new StringBuilder();
    for (int i=0; i<params.length; i+=2) {
        if( i > 0 ) {
            result.append('&');
        }
        try {
            result.
            append(URLEncoder.encode(params[i], StandardCharsets.UTF_8.name())).
            append('=').
            append(URLEncoder.encode(params[i+1], StandardCharsets.UTF_8.name()));
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    return result.toString();
 }

【讨论】:

  • 这不是最好的,但它是一个解决方案。你能告诉我为什么需要 Apache HTTPComponents 吗?据我所知,这是可以在没有任何额外库的情况下实现的。现在作为我接受的唯一答案,似乎很难在 MockMVC 中进行测试
  • 为什么你认为我的解决方案不好?您不需要 HTTPComponents,我只是使用它,因为无论如何我在类路径中都有它。您可以使用任何能够构造 URL 编码的表单实体的库,它只不过是urlencode('name1') + '=' + urlencode(value1)+'&amp;' + ...。您可以编写自己的辅助方法来构造它
  • 我想我的意思是如果在 mockMVC 中有一个实现会更好,这很好,但它仍然不是一种解决方法。有趣的是,我看到我有一段时间没有使用 Java 后回复了
【解决方案2】:

这是一个 Kotlin SpringBoot 示例:

@RunWith(MockitoJUnitRunner::class)
class ApiFormControllerTest {

  lateinit var mvc: MockMvc

  @InjectMocks
  lateinit var apiFormController: ApiFormController

  @Before
  fun setup() {
    mvc = MockMvcBuilders.standaloneSetup(apiFormController).setControllerAdvice(ExceptionAdvice()).build()
  }

  fun MockHttpServletRequestBuilder.withForm(params: Map<String, String>): MockHttpServletRequestBuilder {
    this.contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .content(
            EntityUtils.toString(
                UrlEncodedFormEntity(
                    params.entries.toList().map { BasicNameValuePair(it.key, it.value) }
                )
            )
        )
    return this
  }

  @Test
  fun canSubmitValidForm() {
    mvc.perform(post("/forms").withForm(mapOf("subject" to "hello")))
        .andExpect(status().isOk)
  }

}

【讨论】:

    【解决方案3】:

    您也可以使用我创建的这个小型库:https://github.com/f-lopes/spring-mvc-test-utils/

    在 pom.xml 中添加依赖:

    <dependency>
        <groupId>io.florianlopes</groupId>
        <artifactId>spring-mvc-test-utils</artifactId>
        <version>1.0.1</version>
        <scope>test</scope>
    </dependency>
    

    与 MockMvc 一起使用:

    mockMvc.perform(MockMvcRequestBuilderUtils.postForm("/users", new AddUserForm("John", "Doe", null, new Address(1, "Street", 5222, "New York"))))
        .andExpect(MockMvcResultMatchers.status().isFound())
        .andExpect(MockMvcResultMatchers.redirectedUrl("/users"))
        .andExpect(MockMvcResultMatchers.flash().attribute("message", "success"));
    

    这个库只是根据表单对象将参数添加到 MockMvc 请求中。

    这是我写的详细教程:https://blog.florianlopes.io/tool-for-spring-mockmvcrequestbuilder-forms-tests/

    【讨论】:

    【解决方案4】:

    对于现代 spring (5.3.12),提供的解决方案不起作用。在 MockHttpServletRequestBuilder 中使用param 方法似乎有一个简单而优雅的解决方案:

        mockMvc.perform(post("/some/super/secret/url")
                .param("someparam1", true)
                .param("someparam2", false)
                .with(csrf())
        ).andExpect(status().isOk)
    

    注意:在我使用 Spring Security 时,我需要添加

    .with(csrf())

    以便 CSRF 后处理器允许我的请求。如果不是,Spring 安全将拒绝请求以避免 Cross Site Request Forgery (CSRF) attack.

    【讨论】:

      猜你喜欢
      • 2015-07-29
      • 2019-02-17
      • 1970-01-01
      • 2018-10-21
      • 1970-01-01
      • 2018-10-09
      • 1970-01-01
      • 2018-01-22
      • 2016-11-30
      相关资源
      最近更新 更多