运行集成测试时,Spring Boot 不会解码 URL 参数中的特殊字符

问题描述 投票:0回答:1

我在 Spring Boot RestController 中有一个 GET 端点,它接受电子邮件作为 URL 参数(我知道这不是一个好主意,但我现在无法更改它)。

EmailController.java

package com.example.sospringtestdecodingissue;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/email")
@Slf4j
public class EmailController {

    @GetMapping
    public String inputEmail(@RequestParam String email) {
      log.info("Received input email {}", email);
        return email;
    }
}

当我运行应用程序并通过提供各种电子邮件作为带有特殊字符的输入来测试此控制器时,端点会按预期解码 URL 参数。这是一个例子:

请求-1

curl -X GET --location "http://localhost:8080/api/email?email=test%[email protected]"

响应-1

[email protected]

请求2

curl -X GET --location "http://localhost:8080/api/email?email=test%[email protected]"

响应2

test&[email protected]

请求3

curl -X GET --location "http://localhost:8080/api/[email protected]"

响应3

test [email protected]

请求4

curl -X GET --location "http://localhost:8080/api/email?email=test&[email protected]"

响应4

test

我为此端点创建了一个集成测试,并为上述端点编写了一个测试,使用上述请求和响应作为我的输入测试数据和预期输出。

但是由于某种原因,在集成测试期间电子邮件参数没有被解码,这是集成测试:

EmailControllerMockMvcTest.java

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.stream.Stream;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class EmailControllerMockMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @ParameterizedTest
    @MethodSource("inputValues")
    void testEmailInput(String inputEmail, String expectedOutputEmail) throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/email")
                        .param("email", inputEmail))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.content().string(expectedOutputEmail));
    }

    static Stream<Arguments> inputValues() {
        return Stream.of(
                Arguments.of("test+email.com", "test email.com"),
                Arguments.of("test&email.com", "test"),
                Arguments.of("test%2Bemail.com", "test+email.com"),
                Arguments.of("test%26email.com", "test&email.com")
        );
    }

}

我的印象是这是由于使用了 MockMvc,因为它使用了模拟 Servlet 环境,因此我使用

TestRestTemplate
编写了另一个测试,但令我惊讶的是,我仍然看到相同的行为。

EmailControllerTestRestTemplateIT.java

import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

import java.util.stream.Stream;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class EmailControllerTestRestTemplateIT {

    @Autowired
    private TestRestTemplate testRestTemplate;
    @LocalServerPort
    private Integer port;

    @ParameterizedTest
    @MethodSource("inputValues")
    void testEmailInput(String inputEmail, String expectedOutputEmail) throws Exception {
        ResponseEntity<String> responseEntity = testRestTemplate.exchange("http://localhost:" + port + "/api/email?email={email}",
                HttpMethod.GET,
                null,
                String.class,
                inputEmail);

        Assertions.assertThat(responseEntity.getBody())
                .isEqualTo(expectedOutputEmail);
    }

    static Stream<Arguments> inputValues() {
        return Stream.of(
                Arguments.of("test+email.com", "test email.com"),
                Arguments.of("test&email.com", "test"),
                Arguments.of("test%2Bemail.com", "test+email.com"),
                Arguments.of("test%26email.com", "test&email.com")
        );
    }

}

我在这里有完整源代码的链接以及失败的测试套件 - https://github.com/SaiUpadhyayula/so-spring-test-decoding-issue

如果您愿意,可以查看并尝试一下。

任何人都可以澄清为什么实际服务器和集成测试之间存在这种差异吗?

spring spring-boot spring-test mockmvc spring-test-mvc
1个回答
0
投票

您的测试失败的原因是您的 CURL 请求中发生了 URI 编码,而您在测试中没有考虑到这一点。

我已将您的 RestTemplate 测试修改为我认为应该有效的内容。

@ParameterizedTest
@MethodSource("inputValues")
void testEmailInput(String inputEmail, String expectedOutputEmail) throws Exception {
    URI uri = URI.create("http://localhost:" + port + "/api/email?email=" + inputEmail);
    ResponseEntity<String> responseEntity = testRestTemplate.exchange(uri,
            HttpMethod.GET,
            null,
            String.class);

    Assertions.assertThat(responseEntity.getBody())
            .isEqualTo(expectedOutputEmail);
}
© www.soinside.com 2019 - 2024. All rights reserved.