Как проверить, возвращает ли конечная точка REST число в Spring Boot? - PullRequest
0 голосов
/ 17 мая 2018

У меня есть простой контроллер, который запрашивает число у некоторой случайной службы 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)));
}

1 Ответ

0 голосов
/ 23 мая 2018

Базовый парсер JSON (по умолчанию JsonSmart) выберет «наиболее подходящий» тип данных для представления чисел. Подход, который вы выбрали, почти работает, вы просто должны соответствовать фактическим типам данных, создаваемым анализатором JSON. В вашем примере int и double. Так

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;
        }
    };
}
...