#参考
http://grails.jp/doc/latest/guide/testing.html
http://grails.org/doc/latest/guide/testing.html
はじめに
今回はGrailsのバージョンが2.4です。
標準でSpockでテストを書きます。
テストを実行するためには、以下のコマンドを実行します。
test-app
もしくは test-app unit:
コレで実行可能なユニットテストが実行されます。
他にもオプションなどありますが、基本的に気にしないほうがいいです。テストが実行されなかったりして、混乱のもとになります。
現在自分がGrailsのユニットテストの書き方を勉強中です。
この記事はまだ完成していません。
Qiitaの下書きが埋まってしまったので、とりあえずの公開となります。
今後、内容の修正、追記を都度行います。
#豆知識
ユニットテストなので、Grailsは環境をtestとして、DataSource.groovyとBootstrap.groovyをユニットテストが終わった後に読み込ます。
コレは、ユニットテストを環境(データベース)に依存させないためです。
integrationテストが続いて実行される場合には、その情報が利用されます。
#テスト用ファイルの生成方法
create-domain-class Test
とかcreate-controller Test
コマンド、create-tag-lib Test
を実行すると、それ用のユニットテスト用ファイルが自動で生成される。
デフォルトは以下のようなソースになっている。
package パッケージ名
import grails.test.mixin.TestFor
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions
*/
@TestFor(Test)
class TestSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
}
}
package パッケージ名
import grails.test.mixin.TestFor
import spock.lang.Specification
/**
* See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
*/
@TestFor(TestController)
class TestControllerSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
}
}
#コントローラテスト
package パッケージ名
import grails.converters.JSON
import grails.converters.XML
import grails.plugin.springsecurity.annotation.Secured
import org.springframework.web.multipart.MultipartFile
class TestController {
def hello() {
render "hello"
}
def redirectToHello() {
redirect(controller: 'test', action: 'hello')
}
def methodPrinter() {
render request.method
}
def home() {
render(view: 'homePage', model: [title: 'Hello World'])
}
def showTemplate() {
render(template:"snippet")
}
def showBookDetails() {
// render(model: [title: 'The Nature Of Necessity', author: 'Alvin Plantinga'])
[title: 'The Nature Of Necessity', author: 'Alvin Plantinga']
}
def renderXml() {
render (contentType: 'text/xml') {
book(title:"Great")
}
}
def renderJson() {
render(contentType: "application/json") {
book = "Great"
}
}
def xmlAndJsonRequest(TestCommand t) {
render "Name is ${t.name}"
}
def xmlAndJsonRequestWithoutBinding(){
request.withFormat {
xml {
// なぜかXMLが必ずnullになる。
// Grailsのバグ?
render "The XML Title Is ${request.XML}."
}
json {
render "The JSON Title Is ${request.JSON.title}."
}
}
}
def mimeTypeHandling(){
def data = [Hello:"World"]
request.withFormat {
xml { render data as XML}
json { render data as JSON}
html {data}
}
}
// http://grails.org/doc/latest/ref/Controllers/withForm.html
def formHandling() {
withForm {
render "Good"
}.invalidToken {
render "Bad"
}
}
def uploadFile() {
MultipartFile file = request.getFile("myFile")
if(!file.isEmpty()) {
file.transferTo( new File("/tmp/upload/${file.originalFilename}") )
render "OK"
} else {
render "NG"
}
}
def commandObjectHandling(TestCommand cmd) {
if(cmd.hasErrors()) {
render "Bad"
} else {
render "Good"
}
}
static allowedMethods = [save:'POST']
def save() {
render "saved."
}
def showMessage() {
render g.message(code:'foo.bar')
}
}
class TestCommand {
String name
Integer age
static constraints = {
name(blank:false, minSize: 1)
age(min: 1)
}
}
package パッケージ名
import grails.test.mixin.TestFor
import org.codehaus.groovy.grails.plugins.testing.GrailsMockMultipartFile
import org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerTokensHolder
import spock.lang.Specification
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED
import static javax.servlet.http.HttpServletResponse.SC_OK
/**
* See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
*/
@TestFor(TestController)
class TestControllerSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "基本的なテスト"() {
when:
controller.hello()
then:
response.text == 'hello'
}
void "リダイレクトのテスト"() {
when:
controller.redirectToHello()
then:
response.redirectedUrl == '/test/hello'
}
void "リクエストメソッドの確認"(){
when:
request.method ='POST'
controller.methodPrinter()
then:
response.text == "POST"
}
void "リクエストメソッドの確認(複数バージョン)" () {
setup:
request.method = method
controller.methodPrinter()
expect:
response.text == result
where:
method ||result
'POST' || 'POST'
'GET' || 'GET'
}
void "表示されるviewと渡されるモデルのテスト" (){
when:
controller.home()
then:
view == '/test/homePage'
model.title == 'Hello World'
}
void "Mapを返すアクションのテスト" () {
// 上記のテストと違って、contrller側でmodelとわざわざ指定せずにデーてあ(Map)が返されている場合は
// 以下のようにしてテストする必要がある。
when:
def model = controller.showBookDetails()
then:
model.author == 'Alvin Plantinga'
}
void "テンプレート表示テスト" () {
// テンプレートの中身は直接レスポンスに書き込まれる。
when:
controller.showTemplate()
then:
response.text == "contents of template"
}
void "テストのためにテンプレートの中身を上書きすることもできる"() {
// GroovyPages モックというらしい
when:
views ['/test/_snippet.gsp'] = 'mock template content'
controller.showTemplate()
then:
response.text == 'mock template content'
}
void "XMLのレンダリングのテスト"() {
// XMLもレスポンスに直接書き込まれる。
// response.xmlはXmlSlurper
when:
controller.renderXml()
then:
response.text == "<book title='Great'/>"
response.xml.@title.text() == 'Great'
}
void "JSONのレンダリングのテスト"() {
// JSONもレスポンスに直接書き込まれる
// response.jsonはorg.codehaus.groovy.grails.web.json.JSONElemen
when:
controller.renderJson()
then:
response.text == '{"book":"Great"}'
response.json.book == 'Great'
}
void "xmlリクエストのテスト"() {
when:
request.xml = new Test(name: "koji", age: 29)
controller.xmlAndJsonRequest()
then:
response.text == 'Name is koji'
}
void "jsonリクエストのテスト"() {
when:
request.json = new Test(name: "koji", age: 29)
controller.xmlAndJsonRequest()
then:
response.text == 'Name is koji'
}
// なんかしらんが動かない。
// 送信はちゃんと出来て、コントローラ側でもちゃんとXML扱いになるけど、何故かXMLとしての値が取れずにNULLになる。
// void "xmlリクエストのテスト2"() {
// when:
// //request.xml = "<title>Wool</title>"
// //request.xml = '<book title="The Stand"><title>aaaaaaaa</title></book>'
// request.xml = new Test(name:"koji", age:29)
// controller.xmlAndJsonRequestWithoutBinding()
// then:
// response.text == 'The XML Title Is Hoge!'
// }
void "jsonリクエストのテスト2"() {
when:
request.json = '{title:The Stand}'
controller.xmlAndJsonRequestWithoutBinding()
then:
response.text == 'The JSON Title Is The Stand.'
}
void "マイムタイプの制御テスト(xml)"(){
when:
// XML_CONTENT_TYPEという定数もあるのでそいつを渡してもOK
request.contentType = 'application/xml'
controller.mimeTypeHandling()
then:
response.text == '<?xml version="1.0" encoding="UTF-8"?><map><entry key="Hello">World</entry></map>'
}
void "マイムタイプの制御テスト(json)"(){
when:
// JSON_CONTENT_TYPEという定数もあるのでそいつを渡してもOK
request.contentType = 'application/json'
controller.mimeTypeHandling()
then:
response.text == '{"Hello":"World"}'
}
void "フォームの2重送信テスト"() {
// withForm を利用している場合、トークンが必須となる。
when:
controller.formHandling()
then:
response.text == "Bad"
when:
response.reset()
def tokenHolder = SynchronizerTokensHolder.store(session)
params[SynchronizerTokensHolder.TOKEN_URI] = '/test/formHandling'
params[SynchronizerTokensHolder.TOKEN_KEY] = tokenHolder.generateToken(params[SynchronizerTokensHolder.TOKEN_URI])
controller.formHandling()
then:
response.text == "Good"
}
void "ファイルアップロードテスト"() {
when:
GrailsMockMultipartFile file = new GrailsMockMultipartFile('myFile', 'test.jpg', 'image/jpeg', 'some file data'.bytes)
request.addFile file
controller.uploadFile()
then:
file.targetFileLocation.path == '/tmp/upload/test.jpg'
response.text == "OK"
}
void "Commandオブジェクトのテスト"() {
setup:
def co = new TestCommand(name:name, age:age)
// このテストの段階で、手動でvalidateしておくする必要がある。
// というのも、このテストの段階で手動でコマンドオブジェクト(TestCommand)をnewしてcontrollerに渡しているので、
// controller側ではコマンドオブジェクトのvalid()が実行されないため。
co.validate()
controller.commandObjectHandling(co)
expect:
response.text == result
where:
name |age || result
'koji'| 29 || 'Good'
'' | 29 || 'Bad'
}
void "Commandオブジェクトのテスト(バインディング)"() {
setup:
// こっちの場合、渡したプロパティをcontroller側で判断して自動的にコマンドオブジェクト(TestCommand)にバインディングしてくれて、
// そのタイミングでvalidate()も裏で実行してくれているから、手動でvalidate()を実行する必要はない。
params.name = name
params.age = age
controller.commandObjectHandling()
expect:
response.text == result
where:
name |age || result
'koji'| 29 || 'Good'
'' | 29 || 'Bad'
}
void "allowedMethodsのテスト(saveメソッドはPOSTのみ許可とか)"() {
setup:
request.method = method
controller.save()
expect:
response.status == result
// SC_OKとかはjavax.servlet.http.HttpServletResponseクラスの定数
where:
method||result
'post'|| SC_OK
'get'|| SC_METHOD_NOT_ALLOWED
'put'|| SC_METHOD_NOT_ALLOWED
'update'|| SC_METHOD_NOT_ALLOWED
'delete' || SC_METHOD_NOT_ALLOWED
}
void "タグライブラリg:messageの呼び出しテスト"() {
when:
// このテスト自体に特に意味は無いけど、messageSourceという機能を使えば動的にi18nの編集ができるっぽい
messageSource.addMessage("foo.bar", request.locale, "Hello World")
controller.showMessage()
then:
response.text == "Hello World"
}
}