0

I am unit testing the spring MVC controller in my small app, but getting the following error:

INFO: **Initializing Spring FrameworkServlet ''**
Oct 30, 2015 5:37:38 PM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization started
Oct 30, 2015 5:37:38 PM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization completed in 2 ms
Running com.sky.testmvc.product.controller.ProductSelectionControllerTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.031 sec <<< FAILURE!
testRoot(com.sky.testmvc.product.controller.ProductSelectionControllerTest)  Time elapsed: 0.031 sec  <<< FAILURE!
*java.lang.AssertionError: **Status expected:<200> but was:<204>***
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
    at org.springframework.test.web.servlet.result.StatusResultMatchers$5.match(StatusResultMatchers.java:549)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)

The rest controller is below:

 @RestController
    public class ItemRestController {   

        @Autowired
        ItemService catalogueService;   
        @Autowired
        LocationService locationService;   

        @RequestMapping(value = "/product/{customerId}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
        public ResponseEntity<List<Item>> listCustomerProducts(@PathVariable("customerId") int customerId) {

            Integer customerLocation=(Integer) locationService.findLocationByCustomerId(customerId);
            List<Product> products = catalogueService.findCustomerProducts(customerLocation);

            if(products.isEmpty()){
                return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
            }
            return new ResponseEntity<List<Product>>(products, HttpStatus.OK);
        }   
    }



  @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration(classes = {TestContext.class, AppConfiguration.class}) //load your mvc config classes here

    public class ItemControllerTest {

        private MockMvc mockMvc;
        @Autowired
        private ItemService productServiceMock;
        @Autowired
        private WebApplicationContext webApplicationContext;

        @Before
        public void setUp() {
            Mockito.reset(productServiceMock);
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }



  @Test
public void testRoot() throws Exception 
{      

    Product prod1= new Product(1L,"Orange", new ProductCategory(1,"Sports"),new CustomerLocation(2,"London"));
    Product prod2= new Product(2L,"Banana", new ProductCategory(1,"Sports"),new CustomerLocation(2,"London"));

    when(productServiceMock.findCustomerProducts(1L)).thenReturn(Arrays.asList(prod1, prod2));

    mockMvc.perform(get("/product/{id}", 1L))
    .andExpect(status().isOk())
    .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
    .andExpect(jsonPath("$", hasSize(2)))
            .andExpect(jsonPath("$[0].productId", is(1)))
            .andExpect(jsonPath("$[0].productName", is("Orange")))
            .andExpect(jsonPath("$[1].productId", is(2)))
            .andExpect(jsonPath("$[1].productName", is("Banana")));

    verify(productServiceMock, times(1)).findCustomerProducts(1L);
    verifyNoMoreInteractions(productServiceMock);       
}

    }

See the TestContext below:

 @Configuration
    public class TestContext {

        private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages";

        @Bean
        public MessageSource messageSource() {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

            messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
            messageSource.setUseCodeAsDefaultMessage(true);

            return messageSource;
        }

        @Bean
        public ItemService catalogueService() {
            return Mockito.mock(ItemService .class);
        }
    }


 @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = "com.uk.springmvc")
    public class AppConfiguration extends WebMvcConfigurerAdapter{

        private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/views/";
        private static final String VIEW_RESOLVER_SUFFIX = ".jsp";


        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
            viewResolver.setViewClass(JstlView.class);
            viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
            viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);
            registry.viewResolver(viewResolver);
        }

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/static/**").addResourceLocations("/static/");
        }

        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }

        @Bean
        public SimpleMappingExceptionResolver exceptionResolver() {
            SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

            Properties exceptionMappings = new Properties();

            exceptionMappings.put("java.lang.Exception", "error/error");
            exceptionMappings.put("java.lang.RuntimeException", "error/error");

            exceptionResolver.setExceptionMappings(exceptionMappings);

            Properties statusCodes = new Properties();

            statusCodes.put("error/404", "404");
            statusCodes.put("error/error", "500");

            exceptionResolver.setStatusCodes(statusCodes);

            return exceptionResolver;
        }


    }

As stated earlier, the application works and I can read the json returned via the browser and its displayed in my angularjs view, but the unit test is not working. Error is java.lang.AssertionError: Status expected:<200> but was:<204>. 204 - response is empty ... Could be > Initializing Spring FrameworkServlet '' i.e. framework servlet is not initialized? Not sure. Any help will be appreciated

My Rest Coontroller

@RestController public class ItemRestController {

@Autowired
ItemService catalogueService;  //Service which will do product retrieval

@Autowired
LocationService locationService;  //Service which will retrieve customer location id using customer id

//-------------------Retrieve All Customer Products--------------------------------------------------------

@RequestMapping(value = "/product/{customerId}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
public ResponseEntity<List<Product>> listCustomerProducts(@PathVariable("customerId") int customerId) {

    Integer customerLocation=(Integer) locationService.findLocationByCustomerId(customerId);
    List<Product> products = catalogueService.findCustomerProducts(customerLocation);

    if(products.isEmpty()){
        return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
    }
    return new ResponseEntity<List<Product>>(products, HttpStatus.OK);
}

}

Alex
  • 8,461
  • 6
  • 37
  • 49
user2722614
  • 21
  • 1
  • 7
  • How does the controller you are testing look like? – Kejml Oct 30 '15 at 19:31
  • Hi Kejml, I have updated the original post with the controller. Thanks – user2722614 Oct 30 '15 at 20:37
  • Hm, this could lead to looong question. Obviously your catalogue CatalogueService returns empty result. Where does the service take data from? Some kind of database? Typical setup is, that normal context uses separate database from test context (HSQL is popular choice for testing), so you have to ensure that testing database contains some testing data. This is done either during database startup/on opening connection or in test's @Before/@BeforeClass annotated methods – Kejml Oct 30 '15 at 20:47
  • Or you can mock the service, it depends if you go after unit test or integration test... – Kejml Oct 30 '15 at 20:48
  • I have updated the testRoot() in the test class. The response was populated from the hard coded list. I have tried this many times and ommited it, my bad. But, its still given me a 204. – user2722614 Oct 30 '15 at 21:05

2 Answers2

0

As we can see from the stack trace, the test fails because of the assertion error: it expects HTTP Status 200, but gets 204, which is HTTPStatus.NO_CONTENT, which you return in controller when the list is empty.

        if(products.isEmpty()){
            return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
        }

If you want your controller to return 200 you need your CatalogueService mock to return something...

jny
  • 8,007
  • 3
  • 37
  • 56
  • I have updated the testRoot() in the test class. The response was populated from the hard coded list. I have tried this many times and ommited it, my bad. But, its still given me a 204. – user2722614 Oct 30 '15 at 21:05
  • Tthe questions is, is `products` empty, and if yes, why... And that is what debugger is for... – jny Oct 31 '15 at 00:45
0

In your test, you are mocking ItemService (using productServiceMock), but your controller is calling instance of CatalogueService

Also make sure your mocks are properly injected, debugger is really your best friend here. I guess from your syntax, that you are using Mockito. In such case, mocked object have "$$EnhancedByMockito" in their getClass().getName() method

Kejml
  • 2,114
  • 1
  • 21
  • 31
  • Its ItemService all the way. It was just a typo when trying to be discreet! Sorry! – user2722614 Oct 30 '15 at 21:32
  • Is the mock injected? As I said, debugger can tell you that... or use plain old `System.out.println()` – Kejml Oct 30 '15 at 21:38
  • The following line in ItemControllerTest populates the mock >> when(productServiceMock.findCustomerProducts(1L)).thenReturn(Arrays.asList(prod1, prod2)); How can i test if the mock is injected. I am new to mock testing hence the silly questions. – user2722614 Oct 30 '15 at 21:44
  • No no, I mean in the controller during the test. Can you put breakpoint in your controller and inspect class name of the CatalogueService instance? – Kejml Oct 30 '15 at 21:46