您的位置:首页 > 编程语言 > Java开发

Spring 初探(十一)(Test REST Service)

2017-01-03 20:39 288 查看
下面对于Test REST Service展开介绍

用到的不太熟悉的函数:

org.junit.Before

 在进行一些test之前 后续可能用到一些类似的对象 对于public method施加@Before

 使得method在@Test method之前运行

 当对于标记了@Before的类进行派生时,派生类@Before函数在基类之后运行。

org.junit.Test

 执行Test 的public method 在test 中抛出的异常 将会由JUnit报告为failure

要对RunWith有一个结构上的理解 需要对一些基本概念进行介绍

Junit 提供了tools来进行套件(suite)的运行及结果展示

运行tests 并在console中看结果 需要运行如下代码:
Ex:
org.junit.runner.JUnitCore.runClasses(TestClass1.class,...)


或者:

在test class中添加如下静态代码:
Ex:

public static Test suite(){
return new JUnit4TestAdapter("YourJUnit4TestClass".class);
}


上述方法的运行是通过test runner进行的。

junit.textui.TestRunner

 这是一个通过命令行运行test的工具类。

 其以TestCase的名称为参数进行运行。

 调用进行运行的方法除了在命令行外 还可以调用runner的与run相关的方法,

 如上面的runClasses方法

Spring 提供的SpringJUnit4ClassRunner与常用的Suite

都作为自定义runner 当需要使用这些runners时需要在相应的Test class

前面加@RunWith annotation 并制定使用的runner名称。

其它第三方runner及关于runner的简要介绍参见:
https://github.com/junit-team/junit4/wiki/Test-runners
比较详尽的介绍参见:
http://www.mscharhag.com/java/understanding-junits-runner-architecture
org.springframework.boot.test.SpringApplicationConfiguration

 类一级别的annotation 可以用来指定初始化的ApplicationContext

 用于将相应的应用环境导入到测试类中。

org.springframework.http.MediaType

 可以完成关于传输内容的描述。(如格式及编码)

 具体使用方法见下例

org.springframework.http.HttpMessageConverter(接口)

 converter from and to HTTP requests and responses

org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

 上述HttpMassageConverter的实现 并且可以读写json (使用ObjectMapper进行转换)

 其一般支持的数据形式设定为application/json and application/*+json with UTF-8 character set

 前面的数据格式及后面的字符集格式可以由前述MediaType进行描述。(见下例)

org.springframework.mock.http.MockHttpOutputMessage

 HttpOutputMessage 的mock 实现(用于测试的虚拟实现)

 HttpOutputMessage 仅仅是提供getBody的接口 一般被Http request在客户端实现

 或者response在服务端实现。

 其代码见:

 https://github.com/spring-projects/spring-test-mvc/blob/master/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java

 仅仅是实现测试功能。

org.springframework.test.context.junit4.SpringJunit4ClassRunner

 将要在下面使用的junit runner class

 使用不同runner class的一个原因是其给出的不同annotations

org.springframework.test.context.web.WebAppConfiguration

 class水平annotation 设定被test环境实现的ApplicationContext(应用配置的核心接口)

 应该为WebApplicationContext
 ApplicationContext的基本使用在前面已经做了例证

 如
Spring 初探(五)中的ConfigurableApplicationContext就作为

 其一个派生接口
 一般的如在手动配置xml 调用getBean时使用ApplicationContext进行相应操作

 如增加(自定义)ApplicationListener 时(调用addApplicationListener)

 时使用ConfigurableApplicationContext进行调用

 

 下面即将要提到的WebApplicationContext相对于ApplicationContext的特点

 是提供getServletContext method (其返回标准 Servlet API ServletContext)

 

 ServletContext 定义了Servlet与Servlet Container交互的方法。

org.springframework.test.web.servlet.MockMVC

 对于服务端Spring MVC test的 main入口

 有了这个entry 发送url请求及其检测(RequestMatcher)变得容易

 (提供一整套内置方法)

org.springframework.web.context.WebApplicationContext

 在 org.springframework.test.context.web.WebAppConfiguration已经介绍

这里还使用了部分静态导入的形式

 使用通配符将导入相应类的所有静态方法

org.hamcrest.Matchers.*

 提供了若干个测试描述的方法 这里所使用的是常用的

  is(a thin wrapper for equalTo(value))

 Hamcrest的功能进本上就是测试(assert assertEquals assertThat)

 其相关的简要介绍见:

 http://www.vogella.com/tutorials/Hamcrest/article.html#hamcrestoverview

org.junit.Assert

 这里使用AssertNotNull

下面的三个类给出了测试具体使用到的静态方法。

org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*

 发送请求的get post等方法为该类静态方法

org.springframework.test.web.servlet.result.MockMvcResultMatchers.*

 其定义了如jsonPath(String expression, org.hamcrest.Matcher<T> matcher)

 这种根据expression找到相应的json部分并使用Matcher进行比较的方法

 其中的expression 为JSONPath expression

 其设计是要有一种类似于Xpath解析xml结构的解析json数据的方法

 相应介绍见:

 http://goessner.net/articles/JsonPath/

 可以参看xpath 与json的符号对照表来看。

org.springframework.test.web.servlet.setup.MockBuilders

 这里使用了其定义的函数 webAppContextSetup进行

 webApplicationContext 的应用环境设定。

下面给出使用到的例子及其构建的过程描述:
Ex:

package bookmarks;

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.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

/**
* Created by admin on 2017/1/3.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class BookmarkRestControllerTest {

private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));

private MockMvc mockMvc;

private String userName = "bdussault";

private HttpMessageConverter mappingJackson2HttpMessageConverter;

private Account account;

private List<Bookmark> bookmarkList = new ArrayList<>();

@Autowired
private BookmarkRepository bookmarkRepository;

@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
private AccountRepository accountRepository;

@Autowired
void setConverters(HttpMessageConverter<?>[] converters){
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny().orElse(null);
asser
d24e
tNotNull("the JSON message converter must not be null", this.mappingJackson2HttpMessageConverter);
}

@Before
public void setup(){
this.mockMvc = webAppContextSetup(webApplicationContext).build();

this.bookmarkRepository.deleteAllInBatch();
this.accountRepository.deleteAllInBatch();

this.account = accountRepository.save(new Account(userName, "password"));

this.bookmarkList.add(bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/1/" + userName, "A description")));
this.bookmarkList.add(bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/2/" + userName, "A description")));

}

@Test
public void userNotFound() throws Exception
{
mockMvc.perform(post("/george/bookmarks/").content(this.json(new Bookmark())).
contentType(contentType)).andExpect(status().isNotFound());
}

@Test
public void readSingleBookmark() throws Exception{
mockMvc.perform(get("/" + userName + "/bookmarks/"
+ this.bookmarkList.get(0).getId()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", is(this.bookmarkList.get(0).getId().intValue())))
.andExpect(jsonPath("$.uri", is("http://bookmark.com/1/" + userName)))
.andExpect(jsonPath("$.description", is("A description")));
}

@Test
public void readBookmarks() throws Exception {
mockMvc.perform(get("/" + userName + "/bookmarks"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(this.bookmarkList.get(0).getId().intValue())))
.andExpect(jsonPath("$[0].uri", is("http://bookmark.com/1/" + userName)))
.andExpect(jsonPath("$[0].description", is("A description")))
.andExpect(jsonPath("$[1].id", is(this.bookmarkList.get(1).getId().intValue())))
.andExpect(jsonPath("$[1].uri", is("http://bookmark.com/2/" + userName)))
.andExpect(jsonPath("$[1].description", is("A description")));
}

@Test
public void createBookmark() throws Exception {
String bookmarkJson = json(new Bookmark(
this.account, "http://spring.io", "a bookmark to the best resource for Spring news and information"));

this.mockMvc.perform(post("/" + userName + "/bookmarks")
.contentType(contentType)
.content(bookmarkJson))
.andExpect(status().isCreated());
}

protected String json(Object o)throws IOException{
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}

}


下面对上述构建过程进行描述:

对类进行RunWith指定测试runner

在初始化 contextType的过程中 需要指定主类型及子类型

这里使用了相应名称的函数 所谓主类型 子类型来源于 MINE媒体类型的区分

实际上 MediaType是作为MineType的派生类 所以具有相应初始化格式

一般在http传输中 有关额设定作为传输的参数进行设定

在下面一个小页面给出了一些简单列举:
http://www.cnblogs.com/wuzy/archive/2013/06/04/3118012.html 这个页面的http request headers 的相应属性为:

Content-Type:text/html; charset=utf-8

(chorme)

/前为主属性 后为子属性

在我们例子中需要的是 application/json; charset=utf-8

在前面已经对于Spring @Autowired 进行叙述

这里要补充的是 当对于进行@Autowired修饰的方法 如setter construct等方法

其参数为Collection or Map dependency type时

the container will autowired all beans matching the declared value type

这里对于setConverters的处理正是使用了此特性。

注意HttpMessageConverter<T> 就是使用T作为格式转换的类型

而 MappingJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object>

是实现java对象与message的转换

stream 的findAny方法返回一个Optional<T>对象

当stream不为空时返回包含的stream的某个Optional 否则为empty Optional

这里可能由于不在乎find的顺序使用并行stream比findFirst快。

相应介绍见:
http://stackoverflow.com/questions/25912185/java-8-stream-findany-vs-finding-a-random-element-in-the-stream
setup

 JpaRepository.deleteAllInBatch()

  delete all entities in a batch call

json

 此函数完成object向 json形式String的转换 所用的类及相应方法已经在上面

 提及。

userNotFound

 MockMvcRequestBuilders 静态方法post 生成MockHttpServletRequestBuilder

 对于此builder 调用content (这里是String参数)可以设定 request body
 perform会返回ResultActions对象 可以执行检测

 andExpect perform an expectation

 MockMvcResultMatcher status() 进行 response status assertions

 其isNotFound() assert response status code HttpStatus.NOT_FOUND

 这与输入的 userId george 是并不存在的是相对应的。

 

 到此为止已经多次用到builder函数 之前是 ServletUriComponentsBuilder

 builder的常见形式是其方法作为对于build对象的加工返回builder本身。

readSingleBookmark

 使用的get status content等与userNotFound类似

 所使用的JSONPath是简单的。

readBookmarks

 org.hamcrest.Matchers hasSize是对应collection调用size()的相应

 assertion

剩余的部分是易于理解的。

如果想进一步了解细节,可参看原文链接: http://spring.io/guides/tutorials/bookmarks/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息