I have a simple controller that requests a number from some random REST service and wraps it in a JSON object. These numbers can either be integers or floating point numbers. Thus the consumers of my REST endpoint should expect a floating point value.
This is my controller:
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);
}
}
Now I want to test if the endpoint really returns the number that the REST call returns. Therefore I have written a test that uses 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));
}
}
So the endpoint can either return { "number": 17 }
or { "number": 12.53 }
which is both valid JSON. With .andExpect(jsonPath("number", is(expectedValue)))
I test if the JSON structure really contains the number that was returned by the remote REST service. Unfortunately the test fails for { "number": 17 }
, because jsonPath("number", ...)
passes an integer value to the matcher.
So how can I match both integer an floating point values?
I was thinking of something like the following, but it doesn't work:
@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)));
}