在 Spring Boot 1.4 中测试 Spring MVC 切片的问题
Posted
技术标签:
【中文标题】在 Spring Boot 1.4 中测试 Spring MVC 切片的问题【英文标题】:Issue with testing Spring MVC slice in SpringBoot 1.4 【发布时间】:2016-10-31 07:00:55 【问题描述】:我正在尝试新的 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 来测试控制器。我的观点是 thymeleaf teamplate。我的控制器测试是这样的:
@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() 建议,以便对控制器进行更彻底的测试。
提前致谢。
【问题讨论】:
【参考方案1】:以防万一您添加了以下装饰器但仍然无法正常工作:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SomeTest
@Autowired
private MockMvc mockMvc;
@Test
public void somePositiveTest() throws Exception
mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(status().is2xxSuccessful());
确保您已将以下依赖项添加到您的 pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
【讨论】:
【参考方案2】:我没有像这样在设置阶段实例化 mockmvc 对象,而不是自动连接 MockMvc。
protected void setUp()
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
【讨论】:
【参考方案3】:有兴趣加载完整应用程序的人应尝试结合使用@SpringBootTest
和@AutoConfigureMockMvc
而不是@WebMvcTest
。
我一直在为这个问题苦苦挣扎,但最终我得到了完整的画面。
互联网上的许多教程,以及我目前找到的官方 Spring 文档,声明您可以使用 @WebMvcTest
测试您的控制器;这是完全正确的,但仍然省略了一半的故事。
正如此类注释的 javadoc 所指出的,@WebMvcTest
仅用于测试您的控制器,根本不会加载您应用的所有 bean,这是设计使然。
它甚至与 @Componentscan
这样的显式 bean 扫描注解不兼容。
我建议任何对此事感兴趣的人阅读注释的完整 javadoc(只有 30 行长,并且塞满了浓缩的有用信息),但我会提取一些与我的情况相关的宝石。
来自Annotation Type WebMvcTest
使用此注解将禁用完全自动配置,而仅应用与 MVC 测试相关的配置(即
@Controller
、@ControllerAdvice
、@JsonComponent
过滤器、WebMvcConfigurer
和HandlerMethodArgumentResolver
bean,但不是@Component
,@Service
或@Repository
豆子)。 [...] 如果您希望加载完整的应用程序配置并使用 MockMVC,则应考虑将@SpringBootTest
与@AutoConfigureMockMvc
结合使用,而不是此注释。
实际上,只有@SpringBootTest
+ @AutoConfigureMockMvc
解决了我的问题,所有其他使用@WebMvcTest
的方法都无法加载一些所需的bean。
编辑
我收回我对 Spring 文档所做的评论,因为我不知道使用 @WebMvcTest
时暗示了 slice;实际上,MVC 切片文档清楚地表明,并非所有应用程序都已加载,这是切片的本质。
Custom test slice with Spring Boot 1.4
测试切片是对为您的测试创建的 ApplicationContext 进行分段。通常,如果您想使用 MockMvc 测试控制器,您肯定不想打扰数据层。相反,您可能希望模拟控制器使用的服务并验证所有与 Web 相关的交互是否按预期工作。
【讨论】:
恰到好处。@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc
用于测试类,@Autowired
用于private MockMvc mockMvc;
你是救命的兄弟!【参考方案4】:
您在使用@WebMvcTest
的同时还手动配置了MockMvc
实例。这没有任何意义,因为@WebMvcTest
的主要目的之一是为您自动配置MockMvc
实例。此外,在您的手动配置中,您使用的是standaloneSetup
,这意味着您需要完全配置正在测试的控制器,包括将任何依赖项注入其中。您没有这样做会导致NullPointerException
。
如果您想使用@WebMvcTest
,我建议您这样做,您可以完全删除您的setUp
方法,并使用@Autowired
字段注入自动配置的MockMvc
实例。
然后,要控制ProductController
使用的ProductService
,您可以使用新的@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())));
【讨论】:
将@MockBean
添加到模拟ProductService
会引发NullPointerException
,我已经在github 上发布了我的代码here
我错过了您手动配置MockMvc
实例。我已经更新了答案
Autowiring MockMvc 似乎默认启用了安全性,因此报告java.lang.AssertionError: Status Expected :200 Actual :401
。通过扩展WebSecurityConfigurerAdapter
和覆盖configure(HttpSecurity httpSecurity)
以允许所有对/
和/products
的GET 请求来配置安全性也没有帮助。最后不得不通过控制器测试类上的@AutoConfigureMockMvc(secure=false)
禁用安全性,现在它就像一个魅力。非常感谢您解决了我测试 MVC 切片的核心问题。
我的设置与您的示例完全一样,但我在Service
上获得了Repository
的NoSuchBeanDefinitionException
。对于 @Controller
依赖项使用 @MockBean
是否需要额外的东西?
^ NVM,看起来像一个未解决的问题。 github.com/spring-projects/spring-boot/issues/6663以上是关于在 Spring Boot 1.4 中测试 Spring MVC 切片的问题的主要内容,如果未能解决你的问题,请参考以下文章
使用 h2database 进行 Spring boot 1.4 测试(在每个测试中回滚)