Ошибка модульного тестирования контроллеров Spring MVC, вызывающих REST API

Я выполняю модульное тестирование контроллера Spring MVC в своем небольшом приложении, но получаю следующую ошибку:

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)

Остальной контроллер ниже:

 @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);       
}

    }

См. TestContext ниже:

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


    }

Как указывалось ранее, приложение работает, и я могу прочитать json, возвращенный через браузер, и он отображается в моем представлении angularjs, но модульный тест не работает. Ошибка: java.lang.AssertionError: Ожидаемый статус:‹200>, но был:‹204>. 204 - ответ пуст... Может быть > Инициализация Spring FrameworkServlet '', то есть сервлет фреймворка не инициализирован? Точно сказать не могу. Любая помощь будет оценена

Контроллер моего отдыха

@RestController открытый класс 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);
}

}


person user2722614    schedule 30.10.2015    source источник
comment
Как выглядит контроллер, который вы тестируете?   -  person Kejml    schedule 30.10.2015
comment
Привет, Кеймл, я обновил исходный пост с контроллером. Спасибо   -  person user2722614    schedule 30.10.2015
comment
Хм, это может привести к долгому вопросу. Очевидно, что ваш каталог CatalogueService возвращает пустой результат. Откуда сервис берет данные? Какая-то база данных? Типичная установка такова, что обычный контекст использует базу данных, отдельную от тестового контекста (HSQL является популярным выбором для тестирования), поэтому вы должны убедиться, что тестовая база данных содержит некоторые тестовые данные. Это делается либо во время запуска базы данных/при открытии соединения, либо в аннотированных методах теста @Before/@BeforeClass.   -  person Kejml    schedule 30.10.2015
comment
Или вы можете издеваться над сервисом, это зависит от того, идете ли вы после модульного теста или интеграционного теста...   -  person Kejml    schedule 30.10.2015
comment
Я обновил testRoot() в тестовом классе. Ответ был заполнен из жестко закодированного списка. Я пробовал это много раз и пропустил это, мой плохой. Но мне все равно дали 204.   -  person user2722614    schedule 31.10.2015


Ответы (2)


Как видно из трассировки стека, тест завершается неудачей из-за ошибки утверждения: он ожидает HTTP-статус 200, но получает 204, то есть HTTPStatus.NO_CONTENT, который вы возвращаете в контроллере, когда список пуст.

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

Если вы хотите, чтобы ваш контроллер возвращал 200, вам нужен макет CatalogueService, чтобы что-то вернуть...

person jny    schedule 30.10.2015
comment
Я обновил testRoot() в тестовом классе. Ответ был заполнен из жестко закодированного списка. Я пробовал это много раз и пропустил это, мой плохой. Но мне все равно дали 204. - person user2722614; 31.10.2015
comment
Вопрос в том, является ли products пустым, и если да, то почему... И для этого и нужен отладчик... - person jny; 31.10.2015

В вашем тесте вы издеваетесь над ItemService (используя productServiceMock), но ваш контроллер вызывает экземпляр CatalogueService

Также убедитесь, что ваши макеты правильно введены, отладчик действительно ваш лучший друг. Судя по вашему синтаксису, вы используете Mockito. В таком случае издевательский объект имеет «$ $ EnhancedByMockito» в своем методе getClass().getName().

person Kejml    schedule 30.10.2015
comment
Его ItemService полностью. Это была просто опечатка, когда я пытался быть осторожным! Извини! - person user2722614; 31.10.2015
comment
Макет вводится? Как я уже сказал, отладчик может вам это сказать... или использовать старый добрый System.out.println() - person Kejml; 31.10.2015
comment
Следующая строка в ItemControllerTest заполняет макет ›› when(productServiceMock.findCustomerProducts(1L)).thenReturn(Arrays.asList(prod1, prod2)); Как я могу проверить, введен ли макет. Я новичок в тестировании, отсюда и глупые вопросы. - person user2722614; 31.10.2015
comment
Нет-нет, я имею в виду в контроллере во время теста. Можете ли вы поставить точку останова в своем контроллере и проверить имя класса экземпляра CatalogueService? - person Kejml; 31.10.2015