4

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;
        }
    }
}
Ken
  • 654
  • 8
  • 18

1 Answers1

6

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()))));
Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • Good Idea. It isn't the solution I was hoping for but it is better than what I have. If nothing else comes up I'll accept this answer. – Ken Mar 06 '15 at 12:10
  • honestly the solution is not much. I've mistakenly initially posted Matchers.isA(Integer.class), Matchers.isA(Long.class) but that would not even compile as the type is upper bound. What I've posted now is essentially the same as using only Matchers.isA(Number.class). I'm not proud of this answer, but if it helps you great :) – Master Slave Mar 06 '15 at 13:35