要恰当好处的测试一个Web应用程序,不仅仅是对基础逻辑方法的测试,还应该包含处理的Post、Get请求的情况以及测试表单域绑定到实体参数的情况,即我们需要投入一些实际的HTTP请求,确认它能够正确的处理这些请求。
SpringBoot中有两个可选方案能够实现这种测试:
- Spring Mock MVC:能在一个近似真实的模拟Servlet容器里测试控制器,而不用实际启动应用服务器。
- Web集成测试:在嵌入式Servlet容器(Jetty、Tomcat)里启动应用程序,在真正的服务器里执行测试。
一.模拟SpringMVC(Spring Mock MVC)
Spring的Mock MVC框架模拟了SpringMVC的很多功能,几乎和运行的Servlet容器里面的程序一样
要在测试中设置Mock MVC,可以用MockMvcBuilders,它提供了两个静态方法可以是实现:
- standaloneSetup():构建一个Mock MVC,提供一个或多个手工初始化并注入我们要测试的控制器(需要手工初始化并注入要测试的控制器)
- webAppContextSetup():使用Spring应用程序的上下文来构建Mock MVC,该上下文可以包含一个或多个配置好的控制器(基于WebApplicationContext的实例,由Spring加载控制器和其依赖)
代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class BoyControllerTest {
@Autowired
private WebApplicationContext wac;//注入WebApplicationContext
private MockMvc mockMvc;
/**
* 设置MockMvc
*/
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
}
现在我们有了一个Mock MVC,已经可以开始测试方法了,代码如下:
方法中使用了很多静态方法,包括Spring的MockMvcRequestBuilders和MockMvcResultMatchers里的静态方法,还有Hamcrest库的Matchers里的静态方法
为了方便这里先添加一些静态的import:
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@Test
public void whenQuerySuccess() throws Exception{
mockMvc.perform(get("/boy") // 向/Boy发起一个Get请求
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk()) //希望请求处理成功(isOk()会判断HTTP 200响应码)
.andExpect(view().name("readingList")) //返回视图的逻辑名称为是否为readingList
.andExpect(model().attributeExists("users")) //断定模型中是否包含一个users的属性
.andExpect(model().attribute("users",notNullValue())); //断定模型中users的属性不为空
}
@Test
public void whenGetInfoSuccess() throws Exception{
String result=mockMvc.perform(get("/boy/1"))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(); //获取返回的信息
System.out.println("result:"+result);
}
@Test
public void whenGetInfo() throws Exception{
mockMvc.perform(get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is4xxClientError());
}
二.模拟SpringMVC(RestTemplate)
在真实的服务器里启动应用程序,用真实的Web浏览器访问它,这样比使用模拟的测试引擎更能展现应用程序在用户端的行为,但真实的Web浏览器在真实的服务器上运行测试会很麻烦,而部署到Tomcat或Jetty里配置又不方便,并且几乎不能相互隔离运行。SpringBoot中支持将Tomcat和Jetty这样的嵌入式Servlet容器作为运行中的应用程序的一部分,运用相同的机制,在测试过程中用嵌入式Servlet容器来启动应用程序,这就用到了SpringBoot中的@WebIntegrationTest注解。
@WebIntegrationTest注解:申明希望SpringBoot为测试创建应用程序上下文,并且还启动一个嵌入式的Servlet容器,一旦程序运行在嵌入式容器中,便可以发起真实的http请求
代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SimpleWebTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void getUsers(){
MultiValueMap multiValueMap = new LinkedMultiValueMap();
multiValueMap.add("id",1);
String result=restTemplate.getForObject("/boy/{id}",String.class,1);//get请求
System.out.println(result);
}
}
三.使用Selenium测试页面
使用Selenium测试页面:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SeleniumWebTest {
private static FirefoxDriver browser;
@Value("${local.server.port}")
private int port;
@BeforeClass
public static void openBrowser(){
browser=new FirefoxDriver();
browser.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
@Test
public void addUserToEmptyList(){
String baseUrl="http://localhost:"+port+"/demo/boy";
browser.get(baseUrl);
System.out.println(browser.findElementByTagName("div").getText());
Assert.assertEquals("you have no users in your list",browser.findElementByTagName("div").getText());
}
@AfterClass
public static void closeBrowser(){
browser.quit();
}
}
运行后发现错误,代码如下:
java.lang.IllegalStateException: The path to the driver executable must be set by the webdriver.gecko.driver system property; for more information, see https://github.com/mozilla/geckodriver. The latest version can be downloaded from https://github.com/mozilla/geckodriver/releases
at com.google.common.base.Preconditions.checkState(Preconditions.java:847)
at org.openqa.selenium.remote.service.DriverService.findExecutable(DriverService.java:125)
at org.openqa.selenium.firefox.GeckoDriverService.access$100(GeckoDriverService.java:43)
at org.openqa.selenium.firefox.GeckoDriverService$Builder.findDefaultExecutable(GeckoDriverService.java:168)
at org.openqa.selenium.remote.service.DriverService$Builder.build(DriverService.java:346)
at org.openqa.selenium.firefox.FirefoxDriver.toExecutor(FirefoxDriver.java:168)
at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:125)
at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:103)
at com.example.demo.SeleniumWebTest.openBrowser(SeleniumWebTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
java.lang.NullPointerException
at com.example.demo.SeleniumWebTest.closeBrowser(SeleniumWebTest.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
因为在selenium和Firefox不兼容导致的,需要添加驱动,驱动下载地址:https://github.com/mozilla/geckodriver/releases
设置驱动位置
//设置firefox驱动路经 selenium3.0之后需设置 firefox版本要求大于48
System.setProperty("webdriver.gecko.driver","G:\\JavaTools\\driver\\geckodriver.exe");
browser=new FirefoxDriver();
browser.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);