Unit Testing Spring MVC REST controllers when result Object/json contains a Long field type

Ken picture Ken · Mar 6, 2015 · Viewed 8.3k times · Source

I have a problem when trying to test the JSON output from a Spring REST Service using MockMvcResultMatchers where the returned object should contain a Long value.

The test will only pass when the value within the JSON object is is higher than Integer.MAX_VALUE. This seems a little odd to me as I feel that I should be able to test the full range of applicable values.

I understand that since JSON does not include type information it is performing a best guess at the type at de-serialisation, but I would have expected there to be a way to force the type for extraction when performing the comparison in the MockMvcResultMatchers.

Full code is below but the Test is:

@Test
public void testGetObjectWithLong() throws Exception {
    Long id = 45l;

    ObjectWithLong objWithLong = new ObjectWithLong(id);

    Mockito.when(service.getObjectWithLong(String.valueOf(id))).thenReturn(objWithLong);

    mockMvc.perform(MockMvcRequestBuilders.get("/Test/" + id))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
        .value(Matchers.isA(Long.class)))
    .andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
        .value(Matchers.equalTo(id)));
}

and the Result is:

java.lang.AssertionError: JSON path$longvalue
Expected: is an instance of java.lang.Long
   but: <45> is a java.lang.Integer
at org.springframework.test.util.MatcherAssertionErrors.assertThat(MatcherAssertionErrors.java:80)
...

Any ideas or suggestions as to the proper way to fix this would be appreciated. Obviously I could just add Integer.MAX_VALUE to the id field in the test but that seems fragile.

Thanks in advance.

The following should be self contained apart from the third party libraries

import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RunWith(MockitoJUnitRunner.class)
public class TestControllerTest {

    private MockMvc mockMvc;

    @Mock
    private RandomService service;

    @InjectMocks
    private TestController controller = new TestController();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(controller)
            .setMessageConverters(new MappingJackson2HttpMessageConverter())
            .build();
    }

    @Test
    public void testGetObjectWithLong() throws Exception {
        Long id = 45l;

        ObjectWithLong objWithLong = new ObjectWithLong(id);

        Mockito.when(service.getObjectWithLong(String.valueOf(id))).thenReturn(objWithLong);

        mockMvc.perform(MockMvcRequestBuilders.get("/Test/" + id))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$longvalue").value(Matchers.isA(Long.class)))
        .andExpect(MockMvcResultMatchers.jsonPath("$longvalue").value(Matchers.equalTo(id)));
    }

    @RestController
    @RequestMapping(value = "/Test")
    private class TestController {

        @Autowired
        private RandomService service;

        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        public ObjectWithLong getObjectWithLong(@PathVariable final String id) {
            return service.getObjectWithLong(id);
        }
    }

    @Service
    private class RandomService {
        public ObjectWithLong getObjectWithLong(String id) {
            return new ObjectWithLong(Long.valueOf(id));
        }
    }

    private class ObjectWithLong {

        private Long longvalue;

        public ObjectWithLong(final Long theValue) {
            this.longvalue = theValue;
        }

        public Long getLongvalue() {
            return longvalue;
        }

        public void setLongvalue(Long longvalue) {
            this.longvalue = longvalue;
        }
    }
}

Answer

Master Slave picture Master Slave · Mar 6, 2015

You can use anyOf Matcher along with a Class match against the Number super class and set it up like

.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
        .value(Matchers.isA(Number.class)))
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
        .value(Matchers.anyOf(
            Matchers.equalTo((Number) id),
            Matchers.equalTo((Number) id.intValue()))));