[JavaWeb]_[初级]_[对Jfinal框架的Controller进行单元测试]
Posted infoworld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JavaWeb]_[初级]_[对Jfinal框架的Controller进行单元测试]相关的知识,希望对你有一定的参考价值。
场景
-
开发
JavaWeb
网站时,对Service
层使用JUnit
测试接口还是比较容易的,因为它基本只涉及了简单的数据库操作和业务单元操作,不涉及复杂的页面导航校验,最主要的还是不需要启动应用服务器Tomcat
来运行单元测试。 -
只测试
Service
层是不够的,完整的单元测试也应该包含Controller
(控制层)的测试,这样可以避免耗时的浏览器点击验证流程。那么这一层如何高效的进行单元测试呢?
说明
-
一般应用框架都会有
Controller
,它会用到容器的HttpServletRequest
,HttpServletSession
存储页面数据,还要进行数据校验,组件间的事件通讯等。在JFinal
框架里也有基类com.jfinal.core.Controller
来处理HttpServletRequest
和HttpServletSession
.@Path("/blog") @Before(BlogInterceptor.class) public class BlogController extends Controller @Before(BlogValidator.class) public Integer save() Blog blog = getBean(Blog.class); boolean success = blog.save(); redirect("/blog"); return (success)?blog.getId():0; ...
-
现在看看
save()
方法用到了Controller
的getBean()
和redirect()
方法。getBean
主要是在com.jfinal.core.Injector
类里用到了injectBean()
方法,这个方法主要用到了HttpServletRequest
外部对象。这个request
主要用到了getParameterMap
和getParameter
方法,所以只要Mock
这两个方法返回特定的值就可以了。public static <T> T injectBean(Class<T> beanClass, String beanName, HttpServletRequest request, boolean skipConvertError) ... Map<String, String[]> parasMap = request.getParameterMap(); ... String paraValue = request.getParameter(paraName);
-
之前在
android
开发时使用的是mockito
的Mock
测试框架,通过在pom.xml
里导入. 这里由于需要操作HttpServletRequest
,所以还需要导入servlet-api
。JUnit
无论何时单元测试都是必须的。<!-- junit 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- mockito --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.9.0</version> <scope>test</scope> </dependency> <!-- mockito 的junit适配器 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>3.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency>
-
先说一下
Mock
框架基本上都会实现两点功能:1. 实现接口类的匿名类,并重载接口类的所有方法,添加空默认实现。 2. 实现接口类的匿名类,默认重载调用原方法,可以Mock
某个指定的方法。 这两种方式使用Mock
库都可以指定当方法调用时,可以返回某个指定的值。一般实现Mock
子类,技术上会使用字节码修改或匿名代理两种方法来完成。使用Mock
库可以快速的实现接口的方法而无需自己(通过匿名类)去添加空实现,减少了源码的数量,可以有效的专注业务逻辑上的编码。 -
在创建
Mock
类之后就是填充返回的Map<String, String[]>
类型和Map<String, String>
类型的数据,分别对应着request.getParameterMap()
和request.getParameter
的返回值。也就是可以模拟设置request
里的参数值,方便save()
方法的调用。HttpServletRequest request = Mockito.mock(HttpServletRequest.class); Map<String, String[]> params = new HashMap<>(); params.put("blog.title",null); params.put("blog.content",null); Map<String, String> paramValue = new HashMap<>(); paramValue.put("blog.title","This is Title"); paramValue.put("blog.content","This is content"); when(request.getParameterMap()).thenReturn(params); when(request.getParameter("blog.title")).thenReturn(paramValue.get("blog.title")); when(request.getParameter("blog.content")).thenReturn(paramValue.get("blog.content")); oc.setHttpServletRequest(request);
-
save()
方法调用成功后,接下来就是断言是否保存新数据成功。Integer _id = oc.save(); Assert.assertNotNull(_id); Assert.assertTrue(_id.intValue() > 0); Blog blog = service.findById(_id); log.info("blog id: "+_id); Assert.assertEquals(blog.getStr("content"),"This is content");
-
运行测试用例,单击
testSave()
方法前面的右三角,选择Debug testSave()
, 有以下输出:[INFO]-[Thread: main]-[com.demo.blog.test.TestBlogController.testSave()]: blog id: 34
匿名类
-
使用创建匿名类的方法重载某些不重要的方法。比如
jfinal
这里的redirect
转发到某个页面渲染不需要测,所以BlogController
里调用的redirect("/blog")
不做任何处理。BlogController oc = new BlogController() @Override public void redirect(String url) ;
Mock类
-
spy()
方式的Mock
对象创建,调用原来的方法,参数是自己创建的实例。而mock()
方式创建HttpServletRequest
是完全Mock
一个匿名新子类,调用Mock
的空方法。这里Mock
方法doNothing().when(oc).redirect(anyString())
也是为了调用redirect()
时什么都不做。BlogController oc = Mockito.spy(new BlogController()); doNothing().when(oc).redirect(anyString()); HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
例子
BlogController.java
package com.demo.blog;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.jfinal.core.Controller;
import com.jfinal.core.Path;
import com.demo.common.model.Blog;
/**
* 本 demo 仅表达最为粗浅的 jfinal 用法,更为有价值的实用的企业级用法
* 详见 JFinal 俱乐部: https://jfinal.com/club
*
* 所有 sql 与业务逻辑写在 Service 中,不要放在 Model 中,更不
* 要放在 Controller 中,养成好习惯,有利于大型项目的开发与维护
*/
@Path("/blog")
@Before(BlogInterceptor.class)
public class BlogController extends Controller
@Inject
BlogService service;
public void index()
setAttr("blogPage", service.paginate(getParaToInt(0, 1), 10));
render("blog.html");
public void add()
/**
* save 与 update 的业务逻辑在实际应用中也应该放在 serivce 之中,
* 并要对数据进正确性进行验证,在此仅为了偷懒
*/
@Before(BlogValidator.class)
public Integer save()
Blog blog = getBean(Blog.class);
boolean success = blog.save();
redirect("/blog");
return (success)?blog.getId():0;
public void edit()
setAttr("blog", service.findById(getParaToInt()));
/**
* save 与 update 的业务逻辑在实际应用中也应该放在 serivce 之中,
* 并要对数据进正确性进行验证,在此仅为了偷懒
*/
@Before(BlogValidator.class)
public void update()
getBean(Blog.class).update();
redirect("/blog");
public void delete()
service.deleteById(getParaToInt());
redirect("/blog");
TestBlogController.java
package com.demo.blog.test;
import com.demo.blog.BlogController;
import com.demo.blog.BlogService;
import com.demo.common.model.Blog;
import com.jfinal.aop.Aop;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TestBlogController
private static Logger log = Logger.getLogger(TestBlogController.class);
DruidPlugin dp;
ActiveRecordPlugin arp;
BlogService service;
@BeforeEach
public void onSetUp()
dp = new DruidPlugin("jdbc:mysql://localhost/jfinal_demo?characterEncoding=utf8&useSSL=false&zeroDateTimeBehavior=convertToNull", "root", "");
arp = new ActiveRecordPlugin(dp);
arp.setShowSql(true);
arp.setDevMode(true);
arp.addMapping("blog", Blog.class);
// 与 jfinal web 环境唯一的不同是要手动调用一次相关插件的start()方法
dp.start();
arp.start();
service = Aop.get(BlogService.class);
log.setLevel(Level.INFO);
MockitoAnnotations.openMocks(this);
@AfterEach
public void onTearDown()
arp.stop();
dp.stop();
@Test
public void testSave()
// // 注意,也可以使用匿名类
// BlogController oc = new BlogController()
// @Override
// public void redirect(String url)
//
//
// ;
BlogController oc = Mockito.spy(new BlogController());
doNothing().when(oc).redirect(anyString());
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Map<String, String[]> params = new HashMap<>();
params.put("blog.title",null);
params.put("blog.content",null);
Map<String, String> paramValue = new HashMap<>();
paramValue.put("blog.title","This is Title");
paramValue.put("blog.content","This is content");
when(request.getParameterMap()).thenReturn(params);
when(request.getParameter("blog.title")).thenReturn(paramValue.get("blog.title"));
when(request.getParameter("blog.content")).thenReturn(paramValue.get("blog.content"));
oc.setHttpServletRequest(request);
Integer _id = oc.save();
Assert.assertNotNull(_id);
Assert.assertTrue(_id.intValue() > 0);
Blog blog = service.findById(_id);
log.info("blog id: "+_id);
Assert.assertEquals(blog.getStr("content"),"This is content");
下载
https://download.csdn.net/download/infoworld/85134705
参考
以上是关于[JavaWeb]_[初级]_[对Jfinal框架的Controller进行单元测试]的主要内容,如果未能解决你的问题,请参考以下文章
[JavaWeb]_[初级]_[对Jfinal框架的Controller进行单元测试]
[JavaWeb]_[初级]_[对Jfinal框架的Controller进行单元测试]
[JavaWeb]_[初级]_[搭建jfinal_4.9.15_demo_for_maven开发环境]
[JavaWeb]_[初级]_[搭建jfinal_4.9.15_demo_for_maven开发环境]