Spring boot web 单元测试程序

Posted zhaoyongshui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring boot web 单元测试程序相关的知识,希望对你有一定的参考价值。

一,测试Web应用程序

要恰当地测试一个Web应用程序,需要投入一些实际的HTTP请求,确认它能正确地处理 那些请求。Spring Boot开发者有两个可选的方案能实现这类测试:

  • Spring Mock MVC:能在一个近似真实的模拟Servlet容器里测试控制器,而不用实际启动 应用服务器。
  • Web集成测试:在嵌入式Servlet容器(比如Tomcat或Jetty)里启动应用程序,在真正的应 用服务器里执行测试。

1. 模拟Spring MVC

要在测试里设置Mock MVC,可以使用MockMvcBuilders,该类提供了两个静态方法:

  • standaloneSetup():构建一个Mock MVC,提供一个或多个手工创建并配置的控制器。
  • webAppContextSetup():使用Spring应用程序上下文来构建Mock MVC,该上下文里 可以包含一个或多个配置好的控制器。

两个方法区别:

  • standaloneSetup():手工初始化并注入要测试的控制器,
  • webAppContextSetup():基于一个WebApplicationContext的实例,通常由Spring加载。

前者同单元测试更加接近,你可能只想让它专注于单一控制器的测试,而后者让Spring加载控制 器及其依赖,以便进行完整的集成测试。

/* 代码清单4-2 为集成测试控制器创建Mock MVC */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
classes = ReadingListApplication.class)         //开启Web上下文
@WebAppConfiguration
    public class MockMvcWebTests {
    @Autowired
    private WebApplicationContext webContext;   //注入WebApplicationContext
    private MockMvc mockMvc;
    @Before
    public void setupMockMvc() {
        mockMvc = MockMvcBuilders
        .webAppContextSetup(webContext)        //设置MockMvc
        .build();
    }
}
/* 向/readingList发起一个GET请求 */
@Test
public void homePage() throws Exception {
    mockMvc.perform(get("/readingList"))
        .andExpect(status().isOk())
        .andExpect(view().name("readingList"))
        .andExpect(model().attributeExists("books"))
        .andExpect(model().attribute("books", is(empty())));
}
/* 向/readingList发起一个POST请求 */
@Test
public void postBook() throws Exception {
    mockMvc.perform(post("/readingList")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .param("title", "BOOK TITLE")
        .param("author", "BOOK AUTHOR")
        .param("isbn", "1234567890")
        .param("description", "DESCRIPTION"))
        .andExpect(status().is3xxRedirection())
        .andExpect(header().string("Location", "/readingList"));
/* 验证刚刚的POST请求 */
//配置期望的图书
Book expectedBook = new Book();   
expectedBook.setId(1L);
expectedBook.setReader("craig");
expectedBook.setTitle("BOOK TITLE");
expectedBook.setAuthor("BOOK AUTHOR");
expectedBook.setIsbn("1234567890");
expectedBook.setDescription("DESCRIPTION");
//执行GET请求
mockMvc.perform(get("/readingList"))
    .andExpect(status().isOk())
    .andExpect(view().name("readingList"))
    .andExpect(model().attributeExists("books"))
    .andExpect(model().attribute("books", hasSize(1)))
    .andExpect(model().attribute("books",
    contains(samePropertyValuesAs(expectedBook))));
}

2. 测试Web安全

使用Spring Security

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
  2. 在创建MockMvc实例时运用Spring Security的配置器

    @Before
    public void setupMockMvc() {
        mockMvc = MockMvcBuilders
        .webAppContextSetup(webContext)
        .apply(springSecurity())
        .build();
    }
  3. 使用(具体的安全配置取决于你如何配置Spring Security(或者Spring Boot如何自动配置Spring Security)。)

    场景代码:

    1)请求未经身份验证

    /* 请求未经身份验证,重定向回登录界面 */
    @Test
    public void homePage_unauthenticatedUser() throws Exception {
        mockMvc.perform(get("/"))
        .andExpect(status().is3xxRedirection())
        .andExpect(header().string("Location",
        "http://localhost/login"));
    }

    2)请求经过身份验证 Spring Security提供了两个注解:

    • @WithMockUser:用给定的值创建了一个UserDetails对象,指定用户名、密码和授权。
    • @WithUserDetails:使用事先配置好的UserDetailsService来加载UserDetails对象,根据给定的用户名查找 并返回一个Reader对象。
    /* 经过身份验证的请求,使用@WithMockUser */
    @Test
    @WithMockUser(username="craig",
       password="password",
       roles="READER")
       public void homePage_authenticatedUser() throws Exception {
       ...
    }
    /* 经过身份验证的请求,使用@WithUserDetails */
    @Test
    @WithUserDetails("craig")
    public void homePage_authenticatedUser() throws Exception {
       Reader expectedReader = new Reader();
       expectedReader.setUsername("craig");
       expectedReader.setPassword("password");
       expectedReader.setFullname("Craig Walls");
       mockMvc.perform(get("/"))
           .andExpect(status().isOk())
           .andExpect(view().name("readingList"))
           .andExpect(model().attribute("reader",
           samePropertyValuesAs(expectedReader)))
           .andExpect(model().attribute("books", hasSize(0)))
    }

    此处没有启动Servlet容器来运行这些测试, Spring的Mock MVC取代了实际的Servlet 容器。它比直接调用控制器方法要好,但它并没有真的在Web浏 览器里执行应用程序,验证呈现出的视图。

三、测试运行中的应用程序

Spring Boot支持用嵌入式Servlet容器来启动应用程序。

Spring Boot 的 @WebIntegrationTest 注解就是这么做的。 在测试类上添加@WebIntegrationTest注解,可以声明你不仅希望Spring Boot为测试创建应用程序上下文,还要启 动一个嵌入式的Servlet容器。一旦应用程序运行在嵌入式容器里,你就可以发起真实的HTTP请 求,断言结果了。

/* 代码清单4-5 在服务器里启动应用程序,以Spring的RestTemplate对应用程序发起HTTP请求 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
classes=ReadingListApplication.class)
@WebIntegrationTest
public class SimpleWebTest {
    @Test(expected=HttpClientErrorException.class)
    public void pageNotFound() {
        try {
        RestTemplate rest = new RestTemplate();
        rest.getForObject(
            "http://localhost:8080/bogusPage", String.class);
        fail("Should result in HTTP 404");
        } catch (HttpClientErrorException e) {
        assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode());
        throw e;
        }
    }   
}

1. 用随机端口启动服务器

@WebIntegrationTest(value={"server.port=0"}) 或者 @WebIntegrationTest("server.port=0") 或者 @WebIntegrationTest(randomPort=true)

使用端口

@Value("${local.server.port}")
private int port;
rest.getForObject(
"http://localhost:{port}/bogusPage", String.class, port);

2. 使用Selenium测试html页面

  1. 添加org.seleniumhq.selenium依赖

  2. 代码里使用

    1> 配置

    /* 在Spring Boot里使用Selenium测试的模板 */
     @RunWith(SpringJUnit4ClassRunner.class)
     @SpringApplicationConfiguration(
     classes=ReadingListApplication.class)
     @WebIntegrationTest(randomPort=true)
         public class ServerWebTests {
         private static FirefoxDriver browser;
         @Value("${local.server.port}")
         private int port;
         
         //配置Firefox驱动
         @BeforeClass
         public static void openBrowser() {
             browser = new FirefoxDriver();
             browser.manage().timeouts()
             .implicitlyWait(10, TimeUnit.SECONDS);
         }
         
         //关闭浏览器
         @AfterClass
         public static void closeBrowser() {
             browser.quit();
         }
     }

    2> 测试

    用Selenium测试阅读列表应用程序
     @Test
     public void addBookToEmptyList() {
         String baseUrl = "http://localhost:" + port;
         browser.get(baseUrl);
         assertEquals("You have no books in your book list", 
                         browser.findElementByTagName("div").getText());
                        
         //填充并发送表单                
         browser.findElementByName("title").sendKeys("BOOK TITLE");
         browser.findElementByName("author").sendKeys("BOOK AUTHOR");
         browser.findElementByName("isbn").sendKeys("1234567890");
         browser.findElementByName("description").sendKeys("DESCRIPTION");
         browser.findElementByTagName("form").submit();
         
         //判断列表中是否包含新书
         WebElement dl = browser.findElementByCssSelector("dt.bookHeadline");
         assertEquals("BOOK TITLE by BOOK AUTHOR (ISBN: 1234567890)", dl.getText());
         WebElement dt = browser.findElementByCssSelector("dd.bookDescription");
         assertEquals("DESCRIPTION", dt.getText());
     }

备注:书上版本比较老,下面补充下新版本的测试方法。 例子摘自segmentfault_chenatu的文章 (仅适用spring-boot 1.4版本以后的写法):

直接调用接口函数进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {
    @Autowired
    MessageApi messageApi;
    ...

测试controller:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testControllerMethods() {
        MvcResult result = mockMvc.perform(get("/get-receive-message-abstracts").param("siteId", "webtrn").param("uid", "lucy")
                    .param("limit", "100")).andExpect(status().isOk())
                    .andExpect(jsonPath("$", hasSize(10))).andExpect(jsonPath("$[9].title", is("hello0"))).andReturn();
    }

以上是关于Spring boot web 单元测试程序的主要内容,如果未能解决你的问题,请参考以下文章

基于spring-boot的应用程序的单元+集成测试方案

Spring Boot中编写单元测试

Spring Boot 进阶之Web进阶 学习 - 单元测试

Spring Boot Rest控制器单元测试

Spring Boot单元测试入门实战之关于JUnit

(转)Spring Boot Junit单元测试