【问题标题】:How to test if a REST endpoint returns a number in Spring Boot?如何测试 REST 端点是否在 Spring Boot 中返回数字?
【发布时间】:2018-10-27 14:13:54
【问题描述】:

我有一个简单的控制器,它从一些随机 REST 服务请求一个数字并将其包装在一个 JSON 对象中。这些数字可以是整数或浮点数。因此,我的 REST 端点的消费者应该期望一个浮点值。

这是我的控制器:

import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@Controller
public class NumberController {

    private final RestTemplate restTemplate;

    @Autowired
    public NumberController(final RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping(path = "/number", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public String getNumber() {
        final String number = restTemplate.getForObject("https://example.com/number", String.class);

        return String.format("{\"number\":%s}", number);
    }

}

现在我想测试端点是否真的返回了 REST 调用返回的数字。因此我写了一个使用MockMvc的测试:

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.stream.Stream;

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.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
@SpringJUnitWebConfig
@AutoConfigureMockMvc
class NumberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private RestTemplate restTemplate;

    @ParameterizedTest
    @MethodSource("createTestData")
    void testNumbersEndpoint(final String restServiceValue, final double expectedValue) throws Exception {
        given(restTemplate.getForObject(any(String.class), eq(String.class))).willReturn(restServiceValue);

        mockMvc.perform(get("/number"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("number", is(expectedValue)));
    }

    private static Stream<Arguments> createTestData() {
        return Stream.of(Arguments.of("17", 17.0), Arguments.of("12.53", 12.53));
    }

}

因此端点可以返回{ "number": 17 }{ "number": 12.53 },它们都是有效的JSON。我使用.andExpect(jsonPath("number", is(expectedValue))) 测试 JSON 结构是否真的包含远程 REST 服务返回的数字。不幸的是,{ "number": 17 } 的测试失败了,因为jsonPath("number", ...) 将一个整数值传递给了匹配器。

那么我怎样才能同时匹配整数和浮点值呢?

我正在考虑类似以下的方法,但它不起作用:

@ParameterizedTest
@MethodSource("createTestData")
void testNumbersEndpoint(final String restServiceValue, final Number expectedValue) throws Exception {
    given(restTemplate.getForObject(any(String.class), eq(String.class))).willReturn(restServiceValue);

    mockMvc.perform(get("/number"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("number", is(expectedValue)));
}

private static Stream<Arguments> createTestData() {
    return Stream.of(Arguments.of("17", BigDecimal.valueOf(17)), Arguments.of("12.53", BigDecimal.valueOf(12.53)));
}

【问题讨论】:

    标签: java spring-mvc spring-boot spring-boot-test spring-mvc-test


    【解决方案1】:

    底层 JSON 解析器(默认为 JsonSmart)将选择“最合适”的数据类型来表示数字。您选择的方法几乎可以工作,您只需匹配 JSON 解析器生成的实际数据类型。在您的示例中 intdouble。所以

    private static Stream<Arguments> createTestData() {
        return Stream.of(Arguments.of("17", 17), Arguments.of("12.53", 12.53));
    }
    

    应该可以工作了。

    这是可能的,因为输入值是预先知道的,并且 JSON 解析器的行为也是已知的。如果您需要匹配任意数字类型,那么您可以实现自己的 Matcher 来进行一些条件数据类型转换:

    import java.math.BigDecimal;
    import java.math.BigInteger;
    
    import org.hamcrest.Description;
    import org.hamcrest.Matcher;
    import org.hamcrest.TypeSafeDiagnosingMatcher;
    
    static Matcher<Number> jsonNumber(final BigDecimal d) {
        return new TypeSafeDiagnosingMatcher<Number>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("a numeric value equal to ").appendValue(d);
            }
    
            @Override
            protected boolean matchesSafely(Number item, Description mismatchDescription) {
                BigDecimal actual;
                if (item instanceof BigDecimal) {
                    actual = (BigDecimal) item;
                } else if (item instanceof BigInteger) {
                    actual = new BigDecimal((BigInteger) item);
                } else {
                    actual = BigDecimal.valueOf(item.doubleValue());
                }
    
                if (d.compareTo(actual) == 0) {
                    return true;
                }
    
                mismatchDescription.appendText("numeric value was ").appendValue(item);
                return false;
            }
        };
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-01
      • 2021-06-16
      • 2020-12-03
      • 1970-01-01
      • 1970-01-01
      • 2018-11-19
      • 2021-06-13
      • 1970-01-01
      相关资源
      最近更新 更多