文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

在我早前的文章《使用Spring 和JUnit 进行集成测试》 中,我讨论了如何使用Junit测试一个Service Bean的facade。我同时也在Youtube上发布了一段视频,标题为《使用JUnit测试Spring》。在《Spring Boot 应用服务 – 第三部分 – Spring Data JPA 》这篇文章中, 我展示了如何为Spring MVC 应用测试一个Spring Data JPA repository。

我发布这些帖子和视频距今已经超过一年多了。自从那时开始,对Spring Boot MVC应用的测试的支持有了不少令人兴奋的变革。Spring Boot 1.4发布版本包含了许多令人兴奋的新的测试特性可以供我们使用。

第 1 段(可获 1.36 积分)

在这篇帖子中,我将会带领大家看看那些Spring MVC 的新的测试特性。并且我会给你们展示下如何使用他们。

Spring Boot 1.4 测试增强

在Spring Boot 1.3中,要想为一个Spring MVC应用写测试,会有很多种选择。其中一种设置Spring MVC的方法可以参考我之前的帖子。在这篇帖子里,使用Spring Boot 1.3,需要借助于@RunWith 和@ContextConfiguration两个注解来完成一个业务服务层代码的测试,就像如下代码一样:

 

第 2 段(可获 1.43 积分)
@RunWith(SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = { ProductServiceTestConfig.class } )
public class ProductServiceImplIT {
     private ProductService productService ;
     @Autowired
     public void setProductService( ProductService productService ) {
         this . productService = productService ;
     }
     @ Test
     public void testGetProduct( ) {
         /*Test code*/
     }
}

在这篇帖子里,我用到的另外一个方法,混合使用了@RunWith和@SpringApplicationConfiguration注解来测试一个Spring Data JPA repository,如下所示:

第 3 段(可获 0.84 积分)
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {RepositoryConfiguration.class})
public class ProductRepositoryTest {
     private ProductRepository productRepository;
     @Autowired
     public void setProductRepository(ProductRepository productRepository) {
          this.productRepository = productRepository;
     }

     @Test
     public void testSaveProduct(){
           /*Test code*/
     }
}
第 4 段(可获 0.9 积分)

还有其他几种方式,你可以在Spring的官方博客上查阅。

我在上文中用到的那些测试方式实际上属于集成测试。一个单纯的单元测试不应该创建和加载Spring的上下文。

Spring Boot 1.4 已经替换了那些各种各样的测试方法 ,仅仅通过一个@SpringBootTest注解来完成常规的集成测试。

在Spring Boot 1.4之前,我发现 Spring缺乏一种简单的单元测试的方式。这真的不足为奇。Spring团队总是在创新。Spring和Spring Boot提供了大量的测试选项。这些测试选项由于创新而随时间的推移不断的进化。在Spring Boot 1.4中,Spring的开发者花了一些时间清理了一下测试。他们给我们更加简单的选项来测试Spring Boot应用。例如,在之前版本想要使用单元测试测试一个带有@Autowired注解的外部services的controller而不用去加载Spring上下文,是不可能的。那么有Spring Boot 1.4,一切变的可能。

第 5 段(可获 2 积分)

Spring Boot 1.4 解决的另外一个问题是,可以测试一段代码。这可以轻易完成,甚至不用启动服务器。并且不用启动整个Spring上下文,Spring Boot 1.4通过新的Test Slicing的特性 就可以完成,这个特性被设计成可以至启动一小片的Spring上下文。这时的测试单个的代码片段更加容易了。你现在就可以集中精力去测试你的应用中的特定片段了。比如可以像这样:

  • MVC 片段: 通过@WebMvcTest 注解测试Controller 代码
  • JPA 片段: 通过@DataJpaTest注解测试Spring Data JPA repository 代码
  • JSON 片段: 通过@JsonTest注解 JSON 序列化代码
第 6 段(可获 1.45 积分)

乍一看这可能没有什么了不起。 不过当你有一个大型的应用要测试的话,如果它启动Spring上下文,会很耗费时间的。上下文的加载真的会增加你的构建时间的。

让我们先开始使用这些最新的特性吧。

测试下的应用

我写过一系列的文章,是关于Spring MVC入门的,从这篇 Spring Boot 网页应用 – 第一部分 – Spring 初始化 开始。在这一系列的最后一篇文章里,Spring Boot 网页应用 – 第四部分 – Spring MVC,我创建一个Spring MVC应用,让他可以完成增删改查(CRUD)的操作。

第 7 段(可获 1.24 积分)

在这篇文章里,我将会为大家展示如何为同样的Spring MVC应用里的控制层编写代码。

如果你是一个Spring MVC新手,你应该先去浏览下我之前的关于Spring MVC的系列文章,从这一篇开始。

你也可以在GitHub上下载这个应用的源码,从而跟上这篇文章。

这是一个非常简单的Spring Boot MVC应用的示例代码,它包含了一下几个主要的组件:

  • Product: 一个领域模型对象,同样也是一个JPA的实体
  • IndexController: 返回index.html,这是一个用于向应用根路径发起GET请求的Thymeleaf模板
  • ProductController:包含了几个action方法,这些方法使用ProductService通过数据库仓库模型来完成CRUD操作
  • ProductRepository: 一个Spring Data JPA 仓库
  • ProductService: 一个业务服务门面的接口
  • ProductServiceImpl: 一个加上了@Service注解的业务服务门面的接口实现
第 8 段(可获 1.74 积分)

有了Spring Boot MVC应用,让我们通过为controllers些一些测试代码来开始吧。

Maven依赖

我们正在看到的这些测试特性是在Spring Boot 1.4被引入的。我们将要使用的Spring Boot版本是1.4.0.RELEASE。

下面是我们将要使用的完整的Maven POM文件:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
 
	<groupId>guru.springframework</groupId>
	<artifactId>springmvctest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
 
	<name>springmvctest</name>
	<description>Examples of Spring MVC Test</description>
 
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.0.M3</version>
	</parent>
 
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>
 
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
 
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>bootstrap</artifactId>
			<version>3.3.4</version>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>jquery</artifactId>
			<version>2.1.4</version>
		</dependency>
 
	</dependencies>
 
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
 
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<url>http://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<url>http://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>
</project>

 

第 9 段(可获 3.83 积分)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
 
	<groupId>guru.springframework</groupId>
	<artifactId>springmvctest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
 
	<name>springmvctest</name>
	<description>Examples of Spring MVC Test</description>
 
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.0.M3</version>
	</parent>
 
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>
 
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
 
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>bootstrap</artifactId>
			<version>3.3.4</version>
		</dependency>
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>jquery</artifactId>
			<version>2.1.4</version>
		</dependency>
 
	</dependencies>
 
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
 
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<url>http://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<url>http://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>
</project>

 

第 10 段(可获 4.78 积分)

对Spring MVC 控制层进行单元测试

MockMvc自从Spring 3.2就被引入进来了。它提供了一个强大的方式来模拟Spring MVC用来测试MVC网页应用。通过使用MockMvc,你可以向一个controller发送模拟的HTTP请求,从而不用启动应用服务器就可以测试这个controller的行为。你可以通过MockMvcBuilders的以下两个方法来获取一个MockMvc的实例:

  • standaloneSetup(): 注册一个或多个@Controller实例,并且允许通过编程去配置Spring MVC 的基础架构 从而来构造一个MockMvc的实例。 这跟普通的单元测试很相似,同时也使得一次仅关注一个controller的测试成为可能。
  • webAppContextSetup(): 使用完全被初始化(并且刷新过)了的 WebApplicationContext 来构建一个MockMvc实例。这样使Spring可以加载你的控制层以及它们的所有依赖,从而进行一个完整的集成测试。
第 11 段(可获 1.68 积分)

高级提示:在任何时候如果可能我都会尽量使用standaloneSetup()来完成我的SpringMVC的测试工作。这样你的测试才会是真正的单元测试,并且保持快速!

以下是我们将要测试的IndexController:

IndexController.java

package guru.springframework.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
    @RequestMapping("/")
    String index(){
        return "index";
    }
}

 

第 12 段(可获 1.25 积分)

为了达到我们的目的,我们将要开始使用standaloneSetup()方法来测试这个IndexController。

测试类如下所示:

IndexControllerTest.java

package guru.springframework.controllers;



import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

@RunWith(SpringRunner.class)
public class IndexControllerTest {
       private MockMvc mockMvc;
       @Before
        public void setUp() {
         mockMvc = MockMvcBuilders.standaloneSetup(new IndexController()).build();
        }
        @Test
        public void testIndex() throws Exception{
               this.mockMvc.perform(get("/"))
                    .andExpect(status().isOk())
                    .andExpect(view().name("index"))
                    .andDo(print());
        }
  }

 

第 13 段(可获 1.98 积分)
package guru.springframework.controllers;



import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

@RunWith(SpringRunner.class)
public class IndexControllerTest {
       private MockMvc mockMvc;
       @Before
        public void setUp() {
         mockMvc = MockMvcBuilders.standaloneSetup(new IndexController()).build();
        }
        @Test
        public void testIndex() throws Exception{
               this.mockMvc.perform(get("/"))
                    .andExpect(status().isOk())
                    .andExpect(view().name("index"))
                    .andDo(print());
        }
  }

 

第 14 段(可获 2.15 积分)

上面的测试类是一个JUnit的测试。如果你对JUnit还比较陌生。你就应该先阅读一下我的关于使用JUnit进行单元测试的一系列文章,从这篇文章开始。在上面的测试类中,观察一下新的Spring Boot 1.4的测试运行器,我们在第20行通过@RunWith进行标注。其实在底层,SpringRunner和他的前辈SpringJUnit4ClassRunner 是完全一样的。SpringRunner是SpringJUnit4ClassRunner最新的名字,只是为了让它一眼就能被看明白。

用@Before注解的方法是会在所有以@Test注解的方法之前运行的,在这个方法里,我们在注册了IndexController 实例以后,就使用编码构建了一个MockMvc实例。

第 15 段(可获 1.28 积分)

在以@Test注解的方法里,我们使用了MockMvc实例来验证IndexController的以下行为:

  • HTTP 返回的状态码为200
  • 返回的视图的名字为index

最后,我们通过使用andDo(print()),在控制台得到了如下输出:

Spring MockMvc IndexController Test Output

测试Spring MVC片段

之前我们写的单元测试只是为了完成这个控制层的基本预期。接下来,让我们写一些具体的测试,这一次,我们将要测试ProductController。我们会使用webAppContextSetup()方法来构建 MockMvc实例。

先快速回顾一下,ProductController类是这样的:

ProductController.java

第 16 段(可获 1.19 积分)
package guru.springframework.controllers;

import guru.springframework.domain.Product;
import guru.springframework.services.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

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

    @RequestMapping("product/{id}")
    public String showProduct(@PathVariable Integer id, Model model){
        model.addAttribute("product", productService.getProductById(id));
        return "productshow";
    }

    @RequestMapping("product/edit/{id}")
    public String edit(@PathVariable Integer id, Model model){
        model.addAttribute("product", productService.getProductById(id));
        return "productform";
    }

    @RequestMapping("product/new")
    public String newProduct(Model model){
        model.addAttribute("product", new Product());
        return "productform";
    }

    @RequestMapping(value = "product", method = RequestMethod.POST)
    public String saveProduct(Product product){
        productService.saveProduct(product);
        return "redirect:/product/" + product.getId();
    }

    @RequestMapping("product/delete/{id}")
    public String delete(@PathVariable Integer id){
        productService.deleteProduct(id);
        return "redirect:/products";
    }

}

 

第 17 段(可获 2.2 积分)
package guru.springframework.controllers;

import guru.springframework.domain.Product;
import guru.springframework.services.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

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

    @RequestMapping("product/{id}")
    public String showProduct(@PathVariable Integer id, Model model){
        model.addAttribute("product", productService.getProductById(id));
        return "productshow";
    }

    @RequestMapping("product/edit/{id}")
    public String edit(@PathVariable Integer id, Model model){
        model.addAttribute("product", productService.getProductById(id));
        return "productform";
    }

    @RequestMapping("product/new")
    public String newProduct(Model model){
        model.addAttribute("product", new Product());
        return "productform";
    }

    @RequestMapping(value = "product", method = RequestMethod.POST)
    public String saveProduct(Product product){
        productService.saveProduct(product);
        return "redirect:/product/" + product.getId();
    }

    @RequestMapping("product/delete/{id}")
    public String delete(@PathVariable Integer id){
        productService.deleteProduct(id);
        return "redirect:/products";
    }

}

 

第 18 段(可获 2.91 积分)

我们以测试ProductController.list()方法的行为来开始吧。为了把一个GET请求发送到/product,我们将会执行至以下的验证:

  • ProductService的模拟不为空
  • 返回的HTTP状态码为200
  • 返回的content type为text/html;charset=UTF-8
  • 返回的视图的名字为products
  • 这个视图包含有Spring Framework Guru字符串

下面是测试类代码:

ProductControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
public class ProductControllerTest {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void testList() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("text/html;charset=UTF-8"))
                .andExpect(view().name("products"))
                .andExpect(MockMvcResultMatchers.view().name("products"))
                .andExpect(content().string(Matchers.containsString("Spring Framework Guru")))
                .andDo(print());
    }
}

 

第 19 段(可获 1.78 积分)
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
public class ProductControllerTest {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void testList() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("text/html;charset=UTF-8"))
                .andExpect(view().name("products"))
                .andExpect(MockMvcResultMatchers.view().name("products"))
                .andExpect(content().string(Matchers.containsString("Spring Framework Guru")))
                .andDo(print());
    }
}

 

第 20 段(可获 1.25 积分)

为了测试这个应用的MVC片段(即测试ProductController是否像预期的那样正常工作), 我们使用了@WebMvcTest注解,同时加入了@RunWith(SpringRunner.class)代码。

因为我们打算使用webAppContextSetup() 来构建MockMvc,所以我们在第六行和第七行自动注入(@Autowired)了 WebApplicationContext,从而把它引入进来。接着在第13行,我们将WebApplicationContext作为参数传给了webAppContextSetup()方法从而构建了MockMvc的实例。

回头再来看看我们要测试的ProductController类,请注意这个控制层的类使用了@Autowired注解将ProductService引入。因此,我们使用@MockBean注解来定义一个Mockito的模拟对象来模拟ProductService(第8-9行),他会被传入到这个controller中。如果你对在单元测试中使用模拟还比较陌生,可以看一下我的这篇文章:在单元测试中使用Mockito模拟对象

第 21 段(可获 1.59 积分)

让我们转回到测试代码来看看,在17行我们使用了AssertJ 类库来断言这个模拟的ProductService是非空的。

注意:从 Spring Boot 1.4开始,AssertJ 对 Spring Boot 就是可以开箱即用的,用来提供一个流畅的断言API,计划是要取代JUnit的org.junit.Assert 类。

从第19行到23行,都是在验证我们的期望。就像你看到的那样,在这个测试方法中使用了大量的静态方法,包括 MockMvcRequestBuilders ( get()),MockMvcResultMatchers ( status(),content(),and view()),MockMvcResultMatchers ( match()),和 Hamcrest Matcher’s ( match())。最后的两个match()方法是类似的,它们在我们的测试中执行相同的功能。他们同时存在只是为了演示可以不同的方法而已。

第 22 段(可获 1.56 积分)

我们的测试方法读起来会很自然的。首先它发送了一个GET请求到/products,然后它期望这个请求是成功的( isOk() 方法是对一个HTTP请求返回200状态响应码的断言) ,并且它的内容类型和视图的名字分别是 text/html;charset=UTF-8 和 products。最后,它对视图里包含Spring Framework Guru 这个字符串做了断言。

当所有的期望都通过后,程序在运行到24行时往控制台打印了结果。

这里需要注意的一件重要的事情是,应用是没有部署到一个服务器上的。我们没有使用Tomcat容器,相反的,这个应用在一个模拟的Spring MVC环境里运行,来处理我们使用MockMvc实例发送的HTTP请求。

第 23 段(可获 1.51 积分)

相反的,这个应用在一个模拟的Spring MVC环境里运行,来处理我们使用MockMvc实例发送的HTTP请求。

下面是控制台的输出结果:
Test output of ProductController.list()

控制台完整的测试输出如下:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /products
       Parameters = {}
          Headers = {}
Handler:
             Type = guru.springframework.controllers.ProductController
           Method = public java.lang.String guru.springframework.controllers.ProductController.list(org.springframework.ui.Model)
 
Async:
    Async started = false
     Async result = null
 
Resolved Exception:
             Type = null
 
ModelAndView:
        View name = products
             View = null
        Attribute = products
            value = null
 
FlashMap:
       Attributes = null
 
MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[text/html;charset=UTF-8]}
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>
<html>
<head lang="en">
    <title>Spring Framework Guru</title>     
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="/webjars/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" media="screen" />
    <script src="/webjars/jquery/2.1.4/jquery.min.js"></script>
    <link href="/css/guru.css" rel="stylesheet" media="screen" /> 
</head>
<body>
<div class="container">     
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">Home</a>
                    <ul class="nav navbar-nav">
                        <li><a href="/products">Products</a></li>
                        <li><a href="/product/new">Create Product</a></li>
                    </ul>
                </div>
            </div>
        </nav>
 
        <div class="jumbotron">
            <div class="row text-center">
                <div class="">
                    <h2>Spring Framework Guru</h2>
                    <h3>Spring Boot Web App</h3>
            </div>
            </div>
            <div class="row text-center">
                <img src="/images/NewBannerBOOTS_2.png" width="400" />
            </div>
        </div>   
    </div>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
   Cookies = []

 

第 24 段(可获 3.24 积分)
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /products
       Parameters = {}
          Headers = {}
Handler:
             Type = guru.springframework.controllers.ProductController
           Method = public java.lang.String guru.springframework.controllers.ProductController.list(org.springframework.ui.Model)
 
Async:
    Async started = false
     Async result = null
 
Resolved Exception:
             Type = null
 
ModelAndView:
        View name = products
             View = null
        Attribute = products
            value = null
 
FlashMap:
       Attributes = null
 
MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[text/html;charset=UTF-8]}
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>
<html>
<head lang="en">
    <title>Spring Framework Guru</title>     
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="/webjars/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" media="screen" />
    <script src="/webjars/jquery/2.1.4/jquery.min.js"></script>
    <link href="/css/guru.css" rel="stylesheet" media="screen" /> 
</head>
<body>
<div class="container">     
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">Home</a>
                    <ul class="nav navbar-nav">
                        <li><a href="/products">Products</a></li>
                        <li><a href="/product/new">Create Product</a></li>
                    </ul>
                </div>
            </div>
        </nav>
 
        <div class="jumbotron">
            <div class="row text-center">
                <div class="">
                    <h2>Spring Framework Guru</h2>
                    <h3>Spring Boot Web App</h3>
            </div>
            </div>
            <div class="row text-center">
                <img src="/images/NewBannerBOOTS_2.png" width="400" />
            </div>
        </div>   
    </div>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
   Cookies = []

 

第 25 段(可获 3.6 积分)

使用@Autowired注解的MockMvc来测试Spring MVC片段

现在,让我们测试一下ProductController中的showProduct()方法的行为吧。这一次,我们不手动构建MockMvc,而是在测试中使用一个@Autowired 注解的MockMvc 来让Spring创建、配置和提供一个MockMvc。

以下就是这个测试类在没有任何@Test方法实现的情况下看起来会是什么样的。

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
@AutoConfigureMockMvc(secure=false)
public class ProductControllerTest {
   @Autowired
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;
 
    Product product1;
    @Before
    public void setUpProduct() throws Exception{
        product1 = new Product();
        product1.setId(1);
        product1.setProductId("235268845711068308");
        product1.setDescription("Spring Framework Guru Shirt");
        product1.setPrice(new BigDecimal("18.95"));
        product1.setImageUrl("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg");
 
    }
 
    @Test
    public void testList() throws Exception {
       /*Test code*/
    }
 
    @Test
    public void testShowProduct() throws Exception {
        /*Test code*/
    }
}

 

第 26 段(可获 1.83 积分)
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
@AutoConfigureMockMvc(secure=false)
public class ProductControllerTest {
   @Autowired
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;
 
    Product product1;
    @Before
    public void setUpProduct() throws Exception{
        product1 = new Product();
        product1.setId(1);
        product1.setProductId("235268845711068308");
        product1.setDescription("Spring Framework Guru Shirt");
        product1.setPrice(new BigDecimal("18.95"));
        product1.setImageUrl("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg");
 
    }
 
    @Test
    public void testList() throws Exception {
       /*Test code*/
    }
 
    @Test
    public void testShowProduct() throws Exception {
        /*Test code*/
    }
}

 

第 27 段(可获 1.55 积分)

在上面的测试类中,要注意我们在第5-6行对MockMvc 使用了@Autowired注解,而不是手动去构建它。

一个@Autowired注解的 MockMvc再加上@WebMvcTest(controllers = ProductController.class) 使我们有了一个完全配置好的MockMvc实例,并且还有配置了BASIC授权的Spring安全功能y。

在这一点上,如果我们再次运行 ProductControllerTest.testList() 测试方法,我们就会得到一个授权错误信息,就象这样:

MockHttpServletResponse: 
         Status = 401 
         Error message = Full authentication is required to access this resource

 

第 28 段(可获 1.01 积分)
MockHttpServletResponse :
     Status = 401
     Error message = Full authentication is required to access this resource

我们得到了401的响应代码,是因为Spring Boot为我们自动配置了Spring Security。

如果想要禁止自动配置Spring Security,我们可以使用MockMvc实例去禁止安全配置,在第三行代码里使用 @AutoConfigureMockMvc(secure=false) 即可。

注意,在@Before 注解的方法里,我们创建并且初始化了一个Product的领域对象,它会在 @Test注解的标签里被用到。

这个@Test注解的方法是这样的: 

. . .
@Test
public void testShowProduct() throws Exception {
    assertThat(this.productServiceMock).isNotNull();
    when(productServiceMock.getProductById(1)).thenReturn(product1);
 
    MvcResult result= mockMvc.perform(get("/product/{id}/", 1))
            .andExpect(status().isOk())
            .andExpect(view().name("productshow"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("product"))
            .andExpect(model().attribute("product", hasProperty("id", is(1))))
            .andExpect(model().attribute("product", hasProperty("productId", is("235268845711068308"))))
            .andExpect(model().attribute("product", hasProperty("description", is("Spring Framework Guru Shirt"))))
            .andExpect(model().attribute("product", hasProperty("price", is(new BigDecimal("18.95")))))
            .andExpect(model().attribute("product", hasProperty("imageUrl", is("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg"))))
            .andReturn();
 
 
    MockHttpServletResponse mockResponse=result.getResponse();
    assertThat(mockResponse.getContentType()).isEqualTo("text/html;charset=UTF-8");
 
    Collection<String> responseHeaders = mockResponse.getHeaderNames();
    assertNotNull(responseHeaders);
    assertEquals(1, responseHeaders.size());
    assertEquals("Check for Content-Type header", "Content-Type", responseHeaders.iterator().next());
    String responseAsString=mockResponse.getContentAsString();
    assertTrue(responseAsString.contains("Spring Framework Guru"));
 
    verify(productServiceMock, times(1)).getProductById(1);
    verifyNoMoreInteractions(productServiceMock);
}
. . .

 

第 29 段(可获 2.86 积分)

. . .
@Test
public void testShowProduct() throws Exception {
    assertThat(this.productServiceMock).isNotNull();
    when(productServiceMock.getProductById(1)).thenReturn(product1);
 
    MvcResult result= mockMvc.perform(get("/product/{id}/", 1))
            .andExpect(status().isOk())
            .andExpect(view().name("productshow"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("product"))
            .andExpect(model().attribute("product", hasProperty("id", is(1))))
            .andExpect(model().attribute("product", hasProperty("productId", is("235268845711068308"))))
            .andExpect(model().attribute("product", hasProperty("description", is("Spring Framework Guru Shirt"))))
            .andExpect(model().attribute("product", hasProperty("price", is(new BigDecimal("18.95")))))
            .andExpect(model().attribute("product", hasProperty("imageUrl", is("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg"))))
            .andReturn();
 
 
    MockHttpServletResponse mockResponse=result.getResponse();
    assertThat(mockResponse.getContentType()).isEqualTo("text/html;charset=UTF-8");
 
    Collection<String> responseHeaders = mockResponse.getHeaderNames();
    assertNotNull(responseHeaders);
    assertEquals(1, responseHeaders.size());
    assertEquals("Check for Content-Type header", "Content-Type", responseHeaders.iterator().next());
    String responseAsString=mockResponse.getContentAsString();
    assertTrue(responseAsString.contains("Spring Framework Guru"));
 
    verify(productServiceMock, times(1)).getProductById(1);
    verifyNoMoreInteractions(productServiceMock);
}
. . .

 

第 30 段(可获 2.29 积分)

在上面的测试方法里:

  • 第4行: 执行了一个AssertJ的断言来测试 ProductService的模拟对象不为空。
  • 第5行: 使用Mockito 给ProductService模拟对象的getProductById()方法打桩,并返回初始化过的Product实例。
  • 第8-15行:为给product/{id}发送的一个GET请求进行了如下的校验:
    • HTTP返回了200的状态码
    • 返回的视图的名字是productshow
    • 视图的模型里包含了一个product的属性
    • 这个product的各个属性的跟我们用来初始化Product对象的那些值是一一匹配的。
  • 第16行: 返回结果,为MvcResult的实例
  • 第19-20行: 使用AssertJ来做断言,确保相应的内容类型为text/html;charset=UTF-8
  • 第22-27行: 使用JUnit断言来确保如下:
    • MvcResult返回的响应MockHttpServletResponse对象的头信息不为空
    • 只有个响应头
    • 响应头的名字为Content-Type
    • 这个相应包含Spring Framework Guru 字符串
  • 第29-30行: 使用Mockito来验证ProductService模拟对象的getProductById()方法只被调用了一次,并且在整个测试过程之中ProductService模拟对象的其他任何方法都未被调用过。 
第 31 段(可获 2.43 积分)

完整的测试类代码如下:

ProductControllerTest.java

package guru.springframework.controllers;
 
import guru.springframework.domain.Product;
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.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.math.BigDecimal;
import java.util.Collection;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
@AutoConfigureMockMvc(secure=false)
public class ProductControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;
 
    Product product1;
    @Before
    public void setUpProduct() throws Exception{
 
        product1 = new Product();
        product1.setId(1);
        product1.setProductId("235268845711068308");
        product1.setDescription("Spring Framework Guru Shirt");
        product1.setPrice(new BigDecimal("18.95"));
        product1.setImageUrl("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg");
 
    }
    @Test
    public void testList() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("text/html;charset=UTF-8"))
                .andExpect(view().name("products"))
                .andExpect(MockMvcResultMatchers.view().name("products"))
                .andExpect(content().string(Matchers.containsString("Spring Framework Guru")))
                .andDo(print());
    }
 
    @Test
    public void testShowProduct() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        when(productServiceMock.getProductById(1)).thenReturn(product1);
 
        MvcResult result= mockMvc.perform(get("/product/{id}/", 1))
                .andExpect(status().isOk())
                .andExpect(view().name("productshow"))
                .andExpect(MockMvcResultMatchers.model().attributeExists("product"))
                .andExpect(model().attribute("product", hasProperty("id", is(1))))
                .andExpect(model().attribute("product", hasProperty("productId", is("235268845711068308"))))
                .andExpect(model().attribute("product", hasProperty("description", is("Spring Framework Guru Shirt"))))
                .andExpect(model().attribute("product", hasProperty("price", is(new BigDecimal("18.95")))))
                .andExpect(model().attribute("product", hasProperty("imageUrl", is("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg"))))
                .andReturn();
 
 
        MockHttpServletResponse mockResponse=result.getResponse();
        assertThat(mockResponse.getContentType()).isEqualTo("text/html;charset=UTF-8");
 
        Collection<String> responseHeaders = mockResponse.getHeaderNames();
        assertNotNull(responseHeaders);
        assertEquals(1, responseHeaders.size());
        assertEquals("Check for Content-Type header", "Content-Type", responseHeaders.iterator().next());
        String responseAsString=mockResponse.getContentAsString();
        assertTrue(responseAsString.contains("Spring Framework Guru"));
 
        verify(productServiceMock, times(1)).getProductById(1);
        verifyNoMoreInteractions(productServiceMock);
    }
 
 
}

 

第 32 段(可获 5.71 积分)
package guru.springframework.controllers;
 
import guru.springframework.domain.Product;
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.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.math.BigDecimal;
import java.util.Collection;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
@AutoConfigureMockMvc(secure=false)
public class ProductControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext webApplicationContext;
    @MockBean
    private ProductService productServiceMock;
 
    Product product1;
    @Before
    public void setUpProduct() throws Exception{
 
        product1 = new Product();
        product1.setId(1);
        product1.setProductId("235268845711068308");
        product1.setDescription("Spring Framework Guru Shirt");
        product1.setPrice(new BigDecimal("18.95"));
        product1.setImageUrl("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg");
 
    }
    @Test
    public void testList() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("text/html;charset=UTF-8"))
                .andExpect(view().name("products"))
                .andExpect(MockMvcResultMatchers.view().name("products"))
                .andExpect(content().string(Matchers.containsString("Spring Framework Guru")))
                .andDo(print());
    }
 
    @Test
    public void testShowProduct() throws Exception {
        assertThat(this.productServiceMock).isNotNull();
        when(productServiceMock.getProductById(1)).thenReturn(product1);
 
        MvcResult result= mockMvc.perform(get("/product/{id}/", 1))
                .andExpect(status().isOk())
                .andExpect(view().name("productshow"))
                .andExpect(MockMvcResultMatchers.model().attributeExists("product"))
                .andExpect(model().attribute("product", hasProperty("id", is(1))))
                .andExpect(model().attribute("product", hasProperty("productId", is("235268845711068308"))))
                .andExpect(model().attribute("product", hasProperty("description", is("Spring Framework Guru Shirt"))))
                .andExpect(model().attribute("product", hasProperty("price", is(new BigDecimal("18.95")))))
                .andExpect(model().attribute("product", hasProperty("imageUrl", is("https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg"))))
                .andReturn();
 
 
        MockHttpServletResponse mockResponse=result.getResponse();
        assertThat(mockResponse.getContentType()).isEqualTo("text/html;charset=UTF-8");
 
        Collection<String> responseHeaders = mockResponse.getHeaderNames();
        assertNotNull(responseHeaders);
        assertEquals(1, responseHeaders.size());
        assertEquals("Check for Content-Type header", "Content-Type", responseHeaders.iterator().next());
        String responseAsString=mockResponse.getContentAsString();
        assertTrue(responseAsString.contains("Spring Framework Guru"));
 
        verify(productServiceMock, times(1)).getProductById(1);
        verifyNoMoreInteractions(productServiceMock);
    }
 
 
}

 

第 33 段(可获 6.83 积分)

控制台输出的完整的测试结果如下:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /product/1/
       Parameters = {}
          Headers = {}

Handler:
             Type = guru.springframework.controllers.ProductController
           Method = public java.lang.String guru.springframework.controllers.ProductController.showProduct(java.lang.Integer,org.springframework.ui.Model)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = productshow
             View = null
        Attribute = product
            value = guru.springframework.domain.Product@6069dd38
           errors = []

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[text/html;charset=UTF-8]}
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>

<html>
<head lang="en">

    <title>Spring Framework Guru</title>

    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="/webjars/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" media="screen" />

    <script src="/webjars/jquery/2.1.4/jquery.min.js"></script>

    <link href="/css/guru.css" rel="stylesheet" media="screen" />

</head>
<body>
<div class="container">
    
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">Home</a>
                    <ul class="nav navbar-nav">
                        <li><a href="/products">Products</a></li>
                        <li><a href="/product/new">Create Product</a></li>
                    </ul>

                </div>
            </div>
        </nav>

        <div class="jumbotron">
            <div class="row text-center">
                <div class="">
                    <h2>Spring Framework Guru</h2>

                    <h3>Spring Boot Web App</h3>
            </div>
            </div>
            <div class="row text-center">
                <img src="/images/NewBannerBOOTS_2.png" width="400" />
            </div>
        </div>
    

    <h2>Product Details</h2>
        <div>
            <form class="form-horizontal">
                <div class="form-group">
                    <label class="col-sm-2 control-label">Product Id:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">1</p></div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Description:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">Spring Framework Guru Shirt</p>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Price:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">18.95</p>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Image Url:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg</p>
                    </div>
                </div>
            </form>
    </div>
</div>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
   Cookies = []

 

第 34 段(可获 4.35 积分)
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /product/1/
       Parameters = {}
          Headers = {}

Handler:
             Type = guru.springframework.controllers.ProductController
           Method = public java.lang.String guru.springframework.controllers.ProductController.showProduct(java.lang.Integer,org.springframework.ui.Model)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = productshow
             View = null
        Attribute = product
            value = guru.springframework.domain.Product@6069dd38
           errors = []

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[text/html;charset=UTF-8]}
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>

<html>
<head lang="en">

    <title>Spring Framework Guru</title>

    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="/webjars/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" media="screen" />

    <script src="/webjars/jquery/2.1.4/jquery.min.js"></script>

    <link href="/css/guru.css" rel="stylesheet" media="screen" />

</head>
<body>
<div class="container">
    
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">Home</a>
                    <ul class="nav navbar-nav">
                        <li><a href="/products">Products</a></li>
                        <li><a href="/product/new">Create Product</a></li>
                    </ul>

                </div>
            </div>
        </nav>

        <div class="jumbotron">
            <div class="row text-center">
                <div class="">
                    <h2>Spring Framework Guru</h2>

                    <h3>Spring Boot Web App</h3>
            </div>
            </div>
            <div class="row text-center">
                <img src="/images/NewBannerBOOTS_2.png" width="400" />
            </div>
        </div>
    

    <h2>Product Details</h2>
        <div>
            <form class="form-horizontal">
                <div class="form-group">
                    <label class="col-sm-2 control-label">Product Id:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">1</p></div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Description:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">Spring Framework Guru Shirt</p>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Price:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">18.95</p>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">Image Url:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">https://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg</p>
                    </div>
                </div>
            </form>
    </div>
</div>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
   Cookies = []

 

第 35 段(可获 5.61 积分)

总结

新的@WebMVC注解结合使用MockBean可以让我们我们为Spring MVC应用创建强大并且简单的测试。跟@SpringBootTest注解不同,@WebMvcTest注解会把自动配置给禁用掉。@WebMvcTest只会将Spring MVC的基础架构自动配置,并且仅对使用@Controller,、@ControllerAdvice、@JsonComponent注解的bean,以及Filter、WebMvcConfigurer和HandlerMethodArgumentResolver类型的 bean进行扫描。

当你使用@WebMvcTest注解时,普通的使用@Component、@Service 或 @Repository注解的bean将不会被扫描到,这是和进行完整扫描的@SpringBootTest注解很重要的一个区别。

第 36 段(可获 0.9 积分)

如果你想要加载所有的应用配置并且使用MockMVC,你就应该考虑使用 @SpringBootTest 注解并且加上 @AutoConfigureMockMvc 注解,而不是使用@WebMvcTest注解。我会在将来要写的这个Spring MVC测试系列文章中的另一篇文章中阐述它。我还会帮你大家去探索关于使用 @MockBean 注解加上@DataJpaTest 和 @WebMvcTest来模拟服务类和JPA仓库类的更多内容,还有关于如何使用 MockMvc 和 @JsonTest来对 RESTful controller的 GET 和 POST 请求做单元测试。

第 37 段(可获 0.93 积分)

文章评论

班纳睿
艾玛,终于一个人把这篇长文给翻译完了 :joy:
guyue
本来对spring boot很感兴趣,进来发现被翻译完了, :bowtie: 只能自己看一遍长知识,有补充的地方修改下