首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

精通 Grails 使用 Grails 进行单元测试(4)

精通 Grails 使用 Grails 进行单元测试(4)

理解 ControllerUnitTestCase使用 GrailsUnitTestCase,可以轻松测试域类和服务,但测试控制器还需要其他一些功能。ControllerUnitTestCase 扩展了 GrailsUnitTestCase,所以您仍然可以像以前一样使用 mockForConstraintsTests()、mockDomain() 和 mockLogging()。而且 ControllerUnitTestCase 为您正在测试的控制器创建一个新实例,并将其存储在名为 controller 的变量中。这个 controller 变量可用于在测试期间以编程方式与控制器交互。
要更好地理解核心控制器的功能,在命令提示符处键入 grails generate-controller User。这将 def scaffold = true 替换为控制器代码的完全实现。
在完全实现的 grails-app/controllers/UserController.groovy 文件中,您可以看到,调用 index 操作会重定向到 list 操作,如清单 18 所示:
清单 18. UserController 中默认的 index 操作
1
2
3
4
5
class UserController {

    def index = { redirect(action:list,params:params) }

}




要验证是否按预期发生了重定向,将一个 testIndex() 方法添加到 test/unit/UserControllerTests.groovy,如清单 19 所示:
清单 19. 测试默认的 index 操作
1
2
3
4
5
6
7
8
import grails.test.*

class UserControllerTests extends ControllerUnitTestCase {
    void testIndex() {
      controller.index()
      assertEquals controller.list, controller.redirectArgs["action"]
    }
}




可以看到,您首先调用控制器操作,就像它是另一个控制器上的方法一样。redirect 参数存储一个名为 redirectArgs 的 Map 中。断言验证 action 键是否包含 list 值。(如果操作以一个 render 结束,那么您可以根据名为 renderArgs 的 Map 进行断言)。
现在假设 index 操作稍微先进一些。它检查一个 User 的会话并根据用户是否为 admin 来重定向会话。在 ControllerUnitTestCase 中,session 和 flash 都是 Map,您可以在调用或调用之后的断言之前对它们进行填充。更改 index 操作,如清单 20 所示:
清单 20. 更加先进的 index 操作
1
2
3
4
5
6
7
8
def index = {
  if(session?.user?.role == "admin"){
    redirect(action:list,params:params)
  }else{
    flash.message = "Sorry, you are not authorized to view this list."
    redirect(controller:"home", action:index)
  }
}




要测试这项新功能,更改 UserControllerTests.groovy 中的 testIndex() 方法,如清单 21 所示:
清单 21. 测试 session 和 flash 值
1
2
3
4
5
6
7
8
9
10
11
12
13
void testIndex() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  controller.session.user = jdoe
  controller.index()
  assertEquals "home", controller.redirectArgs["controller"]
  assertTrue controller.flash.message.startsWith("Sorry")

  controller.session.user = suziq
  controller.index()
  assertEquals controller.list, controller.redirectArgs["action"]
}




一些控制器操作需要传入参数。在 ControllerUnitTestCase 中,您可以将值添加到 params Map 中,就像将值添加到 flash 和 session 一样。清单 22 给出了默认的 show 操作:
清单 22. 默认的 show 操作
1
2
3
4
5
6
7
8
9
def show = {
    def userInstance = User.get( params.id )

    if(!userInstance) {
        flash.message = "User not found with id ${params.id}"
        redirect(action:list)
    }
    else { return [ userInstance : userInstance ] }
}




还记得 GrailsUnitTestCase 的 mockDomain() 方法吗?您可以在这里使用它来模拟 User 表,如清单 23 所示:
清单 23. 测试默认的 show 操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void testShow() {
  def jdoe = new User(name:"John Doe",
                      login:"jdoe",
                      password:"password",
                      role:"user")

  def suziq = new User(name:"Suzi Q",
                      login:"suziq",
                      password:"wordpass",
                      role:"admin")

  mockDomain(User, [jdoe, suziq])

  controller.params.id = 2

  // this is the HashMap returned by the show action
  def returnMap = controller.show()
  assertEquals "Suzi Q", returnMap.userInstance.name
}




使用 ControllerUnitTestCase 测试 RESTful Web 服务有时,要测试控制器,您需要访问原始的请求和响应。对于 ControllerUnitTestCase,您可以分别通过 controller.request 和 controller.response 对象获取以下信息: GrailsMockHttpServletRequest 和 GrailsMockHttpServletResponse。
您可以查阅 “” 获取设置 RESTful 服务的指南。再结合 “” 分析结果,您就具备了测试 RESTful Web 服务所需的一切了。
将一个简单的 listXml 操作添加到 UserController,如清单 14 所示。(不要忘记导入 grails.converters 包)。
清单 24. 控制器中的简单 XML 输出
1
2
3
4
5
6
7
8
import grails.converters.*
class UserController {
  def listXml = {
    render User.list() as XML
  }

  // snip...
}




然后将一个 testListXml() 方法添加到 UserControllerTests.groovy,如清单 25 所示:
清单 25. 测试 XML 输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void testListXml() {

  def suziq = new User(name:"Suzi Q",
                      login:"suziq",
                      password:"wordpass",
                      role:"admin")

  mockDomain(User, [suziq])

  controller.listXml()
  def xml = controller.response.contentAsString
  def list = new XmlParser().parseText(xml)
  assertEquals "suziq", list.user.login.text()

  //output
  /*
  <?xml version="1.0" encoding="UTF-8"?>
  <list>
    <user>
      <class>User</class>
      <id>1</id>
      <login>suziq</login>
      <name>Suzi Q</name>
      <password>wordpass</password>
      <role>admin</role>
      <version />
    </user>
  </list>
  */
}




此测试中发生的第一件事是,创建一个新 User 并将其存储在 suziq 变量中,接下来,模拟 User 表,将 suziq 存储为唯一的记录。
当基本设置完成之后,调用 listXml() 操作。要以 String 的形式从操作获取生成的 XML,调用 controller.response.contentAsString 并将其存储在 xml 变量中。
现在,您拥有了一个原始 String。(此 String 的内容仅用于在方法末尾的 output 注释中引用)。调用 new XmlParser().parseText(xml) 会以 groovy.util.Node 对象的形式返回根元素 (<list>)。一旦拥有了 XML 文档的根节点,您就可以使用 GPath 表达式(例如 list.user.login.text())来断言,<login> 元素包含预期的值(在本例中为 suziq)。
可以看到,Grails converters 包简化了 XML 的生成过程,本机 Groovy 库 XmlParser 简化了 XML 的解析过程,而 ControllerUnitTestCase 简化了测试结果 GrailsMockHttpServletResponse 的过程。这是一个强大的技术组合,使得只需短短几行代码就可以进行测试。
返回列表