Проблема с тестированием фрагмента Spring MVC в SpringBoot 1.4

Я пробую новые функции тестирования Spring Boot 1.4 MVC. Имею следующий контроллер.

@Controller
public class ProductController {

  private ProductService productService;

  @Autowired
  public void setProductService(ProductService productService) {
    this.productService = productService;
  }

  @RequestMapping(value = "/products", method = RequestMethod.GET)
  public String list(Model model){
    model.addAttribute("products", productService.listAllProducts());
     return "products";
  }
}

Моя минимальная реализация ProductService:

@Service
public class ProductServiceImpl implements ProductService {
  private ProductRepository productRepository;

  @Autowired
  public void setProductRepository(ProductRepository productRepository) {
    this.productRepository = productRepository;
  }

  @Override
  public Iterable<Product> listAllProducts() {
    return productRepository.findAll();
  }

}

Код ProductRepository:

public interface ProductRepository extends CrudRepository<Product,    
 Integer>{
}

Я пытаюсь использовать новый @WebMvcTest для тестирования контроллера. На мой взгляд, это боевая табличка с тимелистом. И мой тест контроллера таков:

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)

public class ProductControllerTest {
private MockMvc mockMvc;

@Before
public void setUp() {
    ProductController productController= new ProductController();       
    mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
}

@Test
public void testList() throws Exception {        
mockMvc.perform(MockMvcRequestBuilders.get("/products"))                 
.andExpect(MockMvcResultMatchers.status().isOk())                
.andExpect(MockMvcResultMatchers.view().name("products"))             
 .andExpect(MockMvcResultMatchers.model().attributeExists("products"));               
 }
}

Но при запуске теста я получаю эту ошибку.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productController': Unsatisfied dependency expressed through method 'setProductService' parameter 0: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

Мне нужна помощь в решении проблемы, чтобы правильно протестировать ProductController. Мы будем очень признательны за предложения по дополнительному andExpect () для более тщательного тестирования контроллера.

Заранее спасибо.


person user2693135    schedule 28.06.2016    source источник


Ответы (3)


Вы используете @WebMvcTest, одновременно настраивая MockMvc экземпляр вручную. Это не имеет смысла, поскольку одной из основных целей @WebMvcTest является автоматическая настройка MockMvc экземпляра для вас. Кроме того, в ручной настройке вы используете standaloneSetup, что означает, что вам необходимо полностью настроить проверяемый контроллер, включая добавление в него любых зависимостей. Вы не делаете того, что вызывает NullPointerException.

Если вы хотите использовать @WebMvcTest, и я бы порекомендовал вам это сделать, вы можете полностью удалить свой setUp метод и вместо этого ввести автоматически настроенный экземпляр MockMvc с использованием поля @Autowired.

Затем, чтобы управлять ProductService, который используется ProductController, вы можете использовать новую аннотацию @MockBean для создания фиктивного ProductService, который затем будет внедрен в ProductController.

Эти изменения оставляют ваш тестовый класс следующим образом:

package guru.springframework.controllers;

import guru.springframework.services.ProductService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
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 static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    @Test
    public void testList() throws Exception {
      mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                 .andExpect(MockMvcResultMatchers.view().name("products"))
                 .andExpect(MockMvcResultMatchers.model().attributeExists("products"))
               .andExpect(MockMvcResultMatchers.model().attribute("products",
                        Matchers.is(Matchers.empty())));

    }
}
person Andy Wilkinson    schedule 28.06.2016
comment
Добавление @MockBean в mock ProductService вызывает NullPointerException, я разместил свой код на github здесь - person user2693135; 29.06.2016
comment
Я пропустил, что вы вручную настраивали экземпляр MockMvc. Я обновил свой ответ - person Andy Wilkinson; 29.06.2016
comment
Autowiring MockMvc, похоже, по умолчанию включил безопасность, поэтому сообщает java.lang.AssertionError: Status Expected :200 Actual :401. Настройка безопасности путем расширения WebSecurityConfigurerAdapter и переопределения configure(HttpSecurity httpSecurity) для разрешения всех запросов GET на / и /products тоже не помогла. Наконец пришлось отключить безопасность через @AutoConfigureMockMvc(secure=false) на тестовом классе контроллера, и теперь это работает как шарм. Большое спасибо за решение моей основной проблемы тестирования фрагмента MVC. - person user2693135; 29.06.2016
comment
У меня есть установка, которая выглядит точно так же, как ваш пример, но я получаю NoSuchBeanDefinitionException для Repository на Service. Требуется ли что-то дополнительное для использования @MockBean для @Controller зависимостей? - person Snekse; 18.08.2016
comment
^ NVM, похоже, нерешенная проблема. github.com/spring-projects/spring-boot/issues/6663 - person Snekse; 18.08.2016
comment
То же самое. Я использую pring-boot 1.5.2 и делаю то же самое, что и вы, ребята ... и показываю ту же ошибку - person Hinotori; 03.04.2017
comment
даже если это не решило для меня проблему, тем не менее, это отличный ответ. (похоже, что аннотация @WebMvcTest все еще не может запустить загрузку всех beans) - person Antonio; 16.11.2017

Тот, кто заинтересован в загрузке полного приложения, должен попробовать использовать @SpringBootTest в сочетании с @AutoConfigureMockMvc, а не @WebMvcTest.

Я долго боролся с этой проблемой, но, наконец, получил полную картину.
Во многих учебных пособиях в Интернете, , а также в официальной документации Spring, которую я нашел до сих пор, говорится что вы можете тестировать свои контроллеры с помощью @WebMvcTest; это совершенно правильно, хотя и опускается половина истории.
Как указано в javadoc такой аннотации, @WebMvcTest предназначен только для тестирования ваших контроллеров и не будет загружать все компоненты вашего приложения вообще , и это сделано намеренно.
Это несовместимо даже с явными аннотациями сканирования bean-компонентов, такими как @Componentscan.

Я предлагаю всем, кто интересуется этим вопросом, прочитать полную javadoc аннотации (которая состоит всего из 30 строк и наполнена сжатой полезной информацией), но я извлечу пару жемчужин, относящихся к моей ситуации.

из Тип аннотации WebMvcTest

Использование этой аннотации отключит полную автоконфигурацию и вместо этого применит только конфигурацию, относящуюся к тестам MVC (то есть @Controller, @ControllerAdvice, @JsonComponent Filter, WebMvcConfigurer и HandlerMethodArgumentResolver beans, но не @Component, @Service или @Repository beans). [...] Если вы хотите загрузить полную конфигурацию приложения и использовать MockMVC, вам следует рассмотреть @SpringBootTest в сочетании с @AutoConfigureMockMvc, а не эту аннотацию.

И на самом деле, только @SpringBootTest + @AutoConfigureMockMvc устранили мою проблему, все другие подходы, в которых использовался @WebMvcTest, не смогли загрузить некоторые из требуемых bean-компонентов.

РЕДАКТИРОВАТЬ

Я забираю свой комментарий, сделанный по поводу документации Spring, потому что я не знал, что подразумевается срез, когда используется @WebMvcTest; на самом деле документация по слайсу MVC ясно показывает, что не все приложение загружается, что является самой природой слайса.

Пользовательский тестовый фрагмент с Spring Boot 1.4

Разделение теста - это сегментирование ApplicationContext, созданного для вашего теста. Как правило, если вы хотите протестировать контроллер с помощью MockMvc, конечно, вам не стоит возиться с уровнем данных. Вместо этого вы, вероятно, захотите имитировать службу, которую использует ваш контроллер, и проверить, что все веб-взаимодействия работают должным образом.

person Antonio    schedule 16.11.2017
comment
В самый раз. @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc в тестовом классе и @Autowired в private MockMvc mockMvc; - person WesternGun; 05.07.2018
comment
Ты спасатель жизни, братан! - person Juanca; 06.03.2019

Вместо автоматического подключения MockMvc я создал экземпляр объекта mockmvc на этапе настройки следующим образом.

protected void setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
person cammando    schedule 13.04.2019