| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> |
| <head> |
| <!-- Matomo --> |
| <script> |
| var _paq = window._paq = window._paq || []; |
| /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ |
| _paq.push(["setDoNotTrack", true]); |
| _paq.push(["disableCookies"]); |
| _paq.push(['trackPageView']); |
| _paq.push(['enableLinkTracking']); |
| (function() { |
| var u="https://analytics.apache.org/"; |
| _paq.push(['setTrackerUrl', u+'matomo.php']); |
| _paq.push(['setSiteId', '79']); |
| var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; |
| g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); |
| })(); |
| </script> |
| <!-- End Matomo Code --> |
| <title>14 Testing 3.0.6</title> |
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> |
| <link rel="stylesheet" href="../css/main.css" type="text/css" media="screen, print" title="Style" charset="utf-8"/> |
| <link rel="stylesheet" href="../css/pdf.css" type="text/css" media="print" title="PDF" charset="utf-8"/> |
| <script type="text/javascript"> |
| function addJsClass() { |
| var classes = document.body.className.split(" "); |
| classes.push("js"); |
| document.body.className = classes.join(" "); |
| } |
| </script> |
| </head> |
| |
| <body class="body" onload="addJsClass();"> |
| <div id="navigation"> |
| <ul> |
| <li> |
| <div id="nav-summary" onmouseover="toggleNavSummary(false)" onmouseout="toggleNavSummary(true)"> |
| <a href="../guide/index.html" class="button">Table of contents</a> |
| |
| <div id="nav-summary-childs" style="display:none;"> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/introduction.html"><strong>1</strong><span>Introduction</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/gettingStarted.html"><strong>2</strong><span>Getting Started</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/upgrading.html"><strong>3</strong><span>Upgrading from Grails 2.x</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/conf.html"><strong>4</strong><span>Configuration</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/commandLine.html"><strong>5</strong><span>The Command Line</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/GORM.html"><strong>6</strong><span>Object Relational Mapping (GORM)</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/theWebLayer.html"><strong>7</strong><span>The Web Layer</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/traits.html"><strong>8</strong><span>Traits</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/webServices.html"><strong>9</strong><span>Web Services</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/async.html"><strong>10</strong><span>Asynchronous Programming</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/validation.html"><strong>11</strong><span>Validation</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/services.html"><strong>12</strong><span>The Service Layer</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/staticTypeCheckingAndCompilation.html"><strong>13</strong><span>Static Type Checking And Compilation</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/testing.html"><strong>14</strong><span>Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/i18n.html"><strong>15</strong><span>Internationalization</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/security.html"><strong>16</strong><span>Security</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/plugins.html"><strong>17</strong><span>Plugins</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/spring.html"><strong>18</strong><span>Grails and Spring</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/hibernate.html"><strong>19</strong><span>Grails and Hibernate</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/scaffolding.html"><strong>20</strong><span>Scaffolding</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/deployment.html"><strong>21</strong><span>Deployment</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/contributing.html"><strong>22</strong><span>Contributing to Grails</span></a> |
| </div> |
| |
| </div> |
| </div> |
| </li> |
| <li class="separator selected"> |
| <a id="ref-button" onclick="localToggle(); return false;" href="#">Quick Reference</a> |
| </li> |
| </ul> |
| </div> |
| <div id="header"> |
| <div class="images clearfix"> |
| |
| <span id="logo"><a href="http://grails.org" target="_blank"><img alt="Grails Logo" title="The Grails Framework" src="../img/grails.png" border="0"/></a></span> |
| |
| |
| </div> |
| <p>See the light - agile, industrial strength, rapid web application development made easy</p> |
| </div> |
| |
| |
| <table id="colset" border="0" cellpadding="0" cellspacing="0"> |
| <tr> |
| <td id="col1"> |
| <div id="main" class="corner-all"> |
| |
| |
| <div class="toc-item prev-left"><a href="../guide/staticTypeCheckingAndCompilation.html"><< <strong>13</strong><span>Static Type Checking And Compilation</span></a></div> |
| |
| |
| <span id='toggle-col1' class="toggle">(<a href="#" onclick="localToggle(); return false;">Quick Reference</a>)</span> |
| |
| |
| <div class="toc-item next-right"><a href="../guide/i18n.html"><strong>15</strong><span>Internationalization</span> >></a></div> |
| |
| |
| |
| <div class="project"> |
| <h1>14 Testing - Reference Documentation</h1> |
| |
| <p><strong>Authors:</strong> Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari</p> |
| |
| <p><strong>Version:</strong> 3.0.6</p> |
| |
| |
| </div> |
| |
| |
| <div id="table-of-content"> |
| <h2>Table of Contents</h2> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#unitTesting"><strong>14.1</strong><span>Unit Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingControllers"><strong>14.1.1</strong><span>Unit Testing Controllers</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingTagLibraries"><strong>14.1.2</strong><span>Unit Testing Tag Libraries</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingDomains"><strong>14.1.3</strong><span>Unit Testing Domains</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingFilters"><strong>14.1.4</strong><span>Unit Testing Filters</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingURLMappings"><strong>14.1.5</strong><span>Unit Testing URL Mappings</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#mockingCollaborators"><strong>14.1.6</strong><span>Mocking Collaborators</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#mockingCodecs"><strong>14.1.7</strong><span>Mocking Codecs</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestMetaprogramming"><strong>14.1.8</strong><span>Unit Test Metaprogramming</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#integrationTesting"><strong>14.2</strong><span>Integration Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#functionalTesting"><strong>14.3</strong><span>Functional Testing</span></a> |
| </div> |
| |
| </div> |
| |
| |
| |
| <a name="12. Testing"><!-- Legacy link --></a> |
| <h1 id="testing">14 Testing</h1> |
| Automated testing is a key part of Grails. Hence, Grails provides many ways to making testing easier from low level unit testing to high level functional tests. This section details the different capabilities that Grails offers for testing.<p class="paragraph"/><blockquote class="note"> |
| Grails 1.3.x and below used the <code>grails.test.GrailsUnitTestCase</code> class hierarchy for testing in a JUnit 3 style. Grails 2.0.x and above deprecates these test harnesses in favour of mixins that can be applied to a range of different kinds of tests (JUnit 3, JUnit 4, Spock etc.) without subclassing |
| </blockquote><p class="paragraph"/>The first thing to be aware of is that all of the <code>create-*</code> and <code>generate-*</code> commands create <code>unit</code> or <code>integration</code> tests automatically. For example if you run the <a href="../ref/Command Line/create-controller.html" class="commandLine">create-controller</a> command as follows:<p class="paragraph"/><div class="code"><pre>grails create-controller com.acme.app.simple</pre></div><p class="paragraph"/>Grails will create a controller at <code>grails-app/controllers/com/acme/app/SimpleController.groovy</code>, and also a unit test at <code>test/unit/com/acme/app/SimpleControllerTests.groovy</code>. What Grails won't do however is populate the logic inside the test! That is left up to you.<p class="paragraph"/><blockquote class="note"> |
| The default class name suffix is <code>Tests</code> but as of Grails 1.2.2, the suffix of <code>Test</code> is also supported. |
| </blockquote><p class="paragraph"/><h4>Running Tests</h4><p class="paragraph"/>Tests are run with the <a href="../ref/Command Line/test-app.html" class="commandLine">test-app</a> command:<p class="paragraph"/><div class="code"><pre>grails test-app</pre></div><p class="paragraph"/>The command will produce output such as:<p class="paragraph"/><div class="code"><pre>------------------------------------------------------- |
| Running Unit Tests… |
| Running test FooTests...FAILURE |
| Unit Tests Completed in 464ms … |
| -------------------------------------------------------<p class="paragraph"/>Tests failed: 0 errors, 1 failures</pre></div><p class="paragraph"/>whilst showing the reason for each test failure.<p class="paragraph"/><blockquote class="note"> |
| You can force a clean before running tests by passing <code>-clean</code> to the <code>test-app</code> command. |
| </blockquote><p class="paragraph"/>Grails writes both plain text and HTML test reports to the <code>target/test-reports</code> directory, along with the original XML files. The HTML reports are generally the best ones to look at.<p class="paragraph"/>Using Grails' <a href="../guide/single.html#interactiveMode" class="guide">interactive mode</a> confers some distinct advantages when executing tests. First, the tests will execute significantly faster on the second and subsequent runs. Second, a shortcut is available to open the HTML reports in your browser:<p class="paragraph"/><div class="code"><pre>open test-report</pre></div><p class="paragraph"/>You can also run your unit tests from within most IDEs.<p class="paragraph"/><h4>Targeting Tests</h4><p class="paragraph"/>You can selectively target the test(s) to be run in different ways. To run all tests for a controller named <code>SimpleController</code> you would run:<p class="paragraph"/><div class="code"><pre>grails test-app SimpleController</pre></div><p class="paragraph"/>This will run any tests for the class named <code>SimpleController</code>. Wildcards can be used...<p class="paragraph"/><div class="code"><pre>grails test-app *Controller</pre></div><p class="paragraph"/>This will test all classes ending in <code>Controller</code>. Package names can optionally be specified...<p class="paragraph"/><div class="code"><pre>grails test-app some.org.*Controller</pre></div><p class="paragraph"/>or to run all tests in a package...<p class="paragraph"/><div class="code"><pre>grails test-app some.org.*</pre></div><p class="paragraph"/>or to run all tests in a package including subpackages...<p class="paragraph"/><div class="code"><pre>grails test-app some.org.**.*</pre></div><p class="paragraph"/>You can also target particular test methods...<p class="paragraph"/><div class="code"><pre>grails test-app SimpleController.testLogin</pre></div><p class="paragraph"/>This will run the <code>testLogin</code> test in the <code>SimpleController</code> tests. You can specify as many patterns in combination as you like...<p class="paragraph"/><div class="code"><pre>grails test-app some.org.* SimpleController.testLogin BookController</pre></div><p class="paragraph"/><h4>Targeting Test Phases</h4><p class="paragraph"/>In addition to targeting certain tests, you can also target test <em class="italic">phases.</em> By default Grails has two testing phases <code>unit</code> and <code>integration.</code><p class="paragraph"/><blockquote class="note"> |
| Grails 2.x uses <code>phase:type</code> syntax. In Grails 3.0 it was removed, because it made no sense in Gradle context. |
| </blockquote><p class="paragraph"/>To execute <code>unit</code> tests you can run:<p class="paragraph"/><div class="code"><pre>grails test-app -unit</pre></div><p class="paragraph"/>To run <code>integration</code> tests you would run...<p class="paragraph"/><div class="code"><pre>grails test-app -integration</pre></div><p class="paragraph"/><h4>Targeting Tests When Using Phases</h4><p class="paragraph"/>Test and phase targeting can be applied at the same time:<p class="paragraph"/><div class="code"><pre>grails test-app some.org.**.* -unit</pre></div><p class="paragraph"/>This would run all tests in the <code>unit</code> phase that are in the package <code>some.org</code> or a subpackage. |
| |
| |
| <a name="12.1 Unit Testing"><!-- Legacy link --></a> |
| <h2 id="unitTesting">14.1 Unit Testing</h2> |
| Unit testing are tests at the "unit" level. In other words you are testing individual methods or blocks of code without consideration for surrounding infrastructure. Unit tests are typically run without the presence of physical resources that involve I/O such databases, socket connections or files. This is to ensure they run as quick as possible since quick feedback is important.<p class="paragraph"/><h4>The Test Mixins</h4><p class="paragraph"/>Since Grails 2.0, a collection of unit testing mixins is provided by Grails that lets you enhance the behavior of a typical JUnit 3, JUnit 4 or Spock test. The following sections cover the usage of these mixins.<p class="paragraph"/><blockquote class="note"> |
| The previous JUnit 3-style <code>GrailsUnitTestCase</code> class hierarchy is still present in Grails for backwards compatibility, but is now deprecated. The previous documentation on the subject can be found in the <a href="http://grails.org/doc/1.3.x/guide/9.%20Testing.html" target="blank">Grails 1.3.x documentation</a> |
| </blockquote><p class="paragraph"/>You won't normally have to import any of the testing classes because Grails does that for you. But if you find that your IDE for example can't find the classes, here they all are: |
| <ul class="star"> |
| <li><code>grails.test.mixin.TestFor</code></li> |
| <li><code>grails.test.mixin.Mock</code></li> |
| <li><code>grails.test.mixin.TestMixin</code></li> |
| <li><code>grails.test.mixin.support.GrailsUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.domain.DomainClassUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.services.ServiceUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.web.ControllerUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.web.FiltersUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.web.GroovyPageUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.web.UrlMappingsUnitTestMixin</code></li> |
| <li><code>grails.test.mixin.hibernate.HibernateTestMixin</code></li> |
| </ul><p class="paragraph"/>Note that you're only ever likely to use the first two explicitly. The rest are there for reference.<p class="paragraph"/><h4>Test Mixin Basics</h4><p class="paragraph"/>Most testing can be achieved via the <code>TestFor</code> annotation in combination with the <code>Mock</code> annotation for mocking collaborators. For example, to test a controller and associated domains you would define the following:<p class="paragraph"/><div class="code"><pre>@TestFor(BookController) |
| @Mock([Book, Author, BookService])</pre></div><p class="paragraph"/>The <code>TestFor</code> annotation defines the class under test and will automatically create a field for the type of class under test. For example in the above case a "controller" field will be present, however if <code>TestFor</code> was defined for a service a "service" field would be created and so on.<p class="paragraph"/>The <code>Mock</code> annotation creates mock version of any collaborators. There is an in-memory implementation of GORM that will simulate most interactions with the GORM API.<p class="paragraph"/><h4>doWithSpring and doWithConfig callback methods, FreshRuntime annotation</h4><p class="paragraph"/>The <code>doWithSpring</code> callback method can be used to add beans with the BeanBuilder DSL. There is the <code>doWithConfig</code> callback method for changing the grailsApplication.config values before the grailsApplication instance of the test runtime gets initialized.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin<p class="paragraph"/><span class="java-keyword">import</span> org.junit.ClassRule |
| <span class="java-keyword">import</span> org.junit.rules.TestRule<p class="paragraph"/><span class="java-keyword">import</span> spock.lang.Ignore; |
| <span class="java-keyword">import</span> spock.lang.IgnoreRest |
| <span class="java-keyword">import</span> spock.lang.Shared; |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestMixin(GrailsUnitTestMixin) |
| class StaticCallbacksSpec <span class="java-keyword">extends</span> Specification { |
| <span class="java-keyword">static</span> doWithSpring = { |
| myService(MyService) |
| }<p class="paragraph"/> <span class="java-keyword">static</span> doWithConfig(c) { |
| c.myConfigValue = 'Hello' |
| }<p class="paragraph"/> def <span class="java-quote">"grailsApplication is not <span class="java-keyword">null</span>"</span>() { |
| expect: |
| grailsApplication != <span class="java-keyword">null</span> |
| }<p class="paragraph"/> def <span class="java-quote">"doWithSpring callback is executed"</span>() { |
| expect: |
| grailsApplication.mainContext.getBean('myService') != <span class="java-keyword">null</span> |
| }<p class="paragraph"/> def <span class="java-quote">"doWithConfig callback is executed"</span>(){ |
| expect: |
| config.myConfigValue == 'Hello' |
| } |
| }</pre></div><p class="paragraph"/> |
| You can also use these callbacks without "static" together with the <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/FreshRuntime.html" class="api">grails.test.runtime.FreshRuntime</a></code> annotation. |
| In this case, a clean application context and grails application instance is initialized for each test method call.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin |
| <span class="java-keyword">import</span> grails.test.runtime.FreshRuntime;<p class="paragraph"/><span class="java-keyword">import</span> org.junit.ClassRule |
| <span class="java-keyword">import</span> org.junit.rules.TestRule<p class="paragraph"/><span class="java-keyword">import</span> spock.lang.Ignore; |
| <span class="java-keyword">import</span> spock.lang.IgnoreRest |
| <span class="java-keyword">import</span> spock.lang.Shared; |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@FreshRuntime |
| @TestMixin(GrailsUnitTestMixin) |
| class TestInstanceCallbacksSpec <span class="java-keyword">extends</span> Specification { |
| def doWithSpring = { |
| myService(MyService) |
| }<p class="paragraph"/> def doWithConfig(c) { |
| c.myConfigValue = 'Hello' |
| }<p class="paragraph"/> def <span class="java-quote">"grailsApplication is not <span class="java-keyword">null</span>"</span>() { |
| expect: |
| grailsApplication != <span class="java-keyword">null</span> |
| }<p class="paragraph"/> def <span class="java-quote">"doWithSpring callback is executed"</span>() { |
| expect: |
| grailsApplication.mainContext.getBean('myService') != <span class="java-keyword">null</span> |
| }<p class="paragraph"/> def <span class="java-quote">"doWithConfig callback is executed"</span>(){ |
| expect: |
| config.myConfigValue == 'Hello' |
| } |
| }</pre></div><p class="paragraph"/><p class="paragraph"/>You can use <code><a href="https://grails.apache.org/docs/3.0.x/api/org/grails/spring/beans/factory/InstanceFactoryBean.html" class="api">org.grails.spring.beans.factory.InstanceFactoryBean</a></code> together with doWithSpring and the <a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/FreshRuntime.html" class="api">FreshRuntime</a> annotation to mock beans in tests.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin |
| <span class="java-keyword">import</span> grails.test.runtime.FreshRuntime<p class="paragraph"/><span class="java-keyword">import</span> org.grails.spring.beans.factory.InstanceFactoryBean |
| <span class="java-keyword">import</span> org.junit.ClassRule<p class="paragraph"/><span class="java-keyword">import</span> spock.lang.Shared |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@FreshRuntime |
| @TestMixin(GrailsUnitTestMixin) |
| class MockedBeanSpec <span class="java-keyword">extends</span> Specification { |
| def myService=Mock(MyService)<p class="paragraph"/> def doWithSpring = { |
| myService(InstanceFactoryBean, myService, MyService) |
| }<p class="paragraph"/> def <span class="java-quote">"doWithSpring callback is executed"</span>() { |
| when: |
| def myServiceBean=grailsApplication.mainContext.getBean('myService') |
| myServiceBean.prova() |
| then: |
| 1 * myService.prova() >> { <span class="java-keyword">true</span> } |
| } |
| }</pre></div><p class="paragraph"/><h4>The DirtiesRuntime annotation</h4><p class="paragraph"/>Test methods may be marked with the <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/DirtiesRuntime.html" class="api">grails.test.runtime.DirtiesRuntime</a></code> annotation to indicate that the test modifies the runtime in ways which might be problematic for other tests and as such the runtime should be refreshed after this test method runs.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> grails.test.runtime.DirtiesRuntime<p class="paragraph"/>@TestFor(PersonController) |
| class PersonControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> @DirtiesRuntime |
| void <span class="java-quote">"a test method which modifies the runtime"</span>() { |
| when: |
| Person.metaClass.someMethod = { … } |
| // ...<p class="paragraph"/> then: |
| // … |
| }<p class="paragraph"/> void <span class="java-quote">"a test method which should not be affected by the previous test method"</span>() { |
| // … |
| } |
| }</pre></div><p class="paragraph"/><h4>Sharing test runtime grailsApplication instance and beans for several test classes</h4><p class="paragraph"/>It's possible to share a single grailsApplication instance and beans for several test classes. |
| This feature is activated by the <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/SharedRuntime.html" class="api">SharedRuntime</a></code> annotation. This annotation takes an optional class parameter |
| implements <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/SharedRuntimeConfigurer.html" class="api">SharedRuntimeConfigurer</a></code> interface. All test classes referencing the same SharedRuntimeConfigurer implementation |
| class will share the same runtime during a single test run. |
| The value class for SharedRuntimeConfigurer annotation can also implement <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/TestEventInterceptor.html" class="api">TestEventInterceptor</a></code> . In this case the instance of the class |
| will be registered as a test event interceptor for the test runtime.<p class="paragraph"/> |
| <h4>Loading application beans in unit tests</h4><p class="paragraph"/>Adding <code>static loadExternalBeans = true</code> field definition to a unit test class makes the Grails unit test runtime load all bean definitions from <code>grails-app/conf/spring/resources.groovy</code> and <code>grails-app/conf/spring/resources.xml</code> files.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> spock.lang.Issue |
| <span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin<p class="paragraph"/>@TestMixin(GrailsUnitTestMixin) |
| class LoadExternalBeansSpec <span class="java-keyword">extends</span> Specification { |
| <span class="java-keyword">static</span> loadExternalBeans = <span class="java-keyword">true</span><p class="paragraph"/> void <span class="java-quote">"should load external beans"</span>(){ |
| expect: |
| applicationContext.getBean('simpleBean') == 'Hello world!' |
| } |
| }</pre></div> |
| |
| |
| <a name="12.1.1 Unit Testing Controllers"><!-- Legacy link --></a> |
| <h2 id="unitTestingControllers">14.1.1 Unit Testing Controllers</h2> |
| <h4>The Basics</h4><p class="paragraph"/>You use the <code>grails.test.mixin.TestFor</code> annotation to unit test controllers. Using <code>TestFor</code> in this manner activates the <code>grails.test.mixin.web.ControllerUnitTestMixin</code> and its associated API. For example:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| } |
| }</pre></div><p class="paragraph"/>Adding the <code>TestFor</code> annotation to a controller causes a new <code>controller</code> field to be automatically created for the controller under test.<p class="paragraph"/><blockquote class="note"> |
| The <code>TestFor</code> annotation will also automatically annotate any public methods starting with "test" with JUnit 4's @Test annotation. If any of your test method don't start with "test" just add this manually |
| </blockquote><p class="paragraph"/>To test the simplest "Hello World"-style example you can do the following:<p class="paragraph"/><div class="code"><pre>// Test class |
| class SimpleController { |
| def hello() { |
| render <span class="java-quote">"hello"</span> |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test hello"</span>() { |
| when: |
| controller.hello()<p class="paragraph"/> then: |
| response.text == 'hello' |
| } |
| }</pre></div><p class="paragraph"/>The <code>response</code> object is an instance of <code>GrailsMockHttpServletResponse</code> (from the package <code>org.codehaus.groovy.grails.plugins.testing</code>) which extends Spring's <code>MockHttpServletResponse</code> class and has a number of useful methods for inspecting the state of the response.<p class="paragraph"/>For example to test a redirect you can use the <code>redirectedUrl</code> property:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| def index() { |
| redirect action: 'hello' |
| } |
| … |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test index'() { |
| when: |
| controller.index()<p class="paragraph"/> then: |
| response.redirectedUrl == '/simple/hello' |
| } |
| }</pre></div><p class="paragraph"/>Many actions make use of the parameter data associated with the request. For example, the 'sort', 'max', and 'offset' parameters are quite common. Providing these in the test is as simple as adding appropriate values to a special <code>params</code> variable:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(PersonController) |
| class PersonControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test list'() { |
| when: |
| params.sort = 'name' |
| params.max = 20 |
| params.offset = 0 |
| controller.list()<p class="paragraph"/> then: |
| // … |
| } |
| }</pre></div><p class="paragraph"/>You can even control what type of request the controller action sees by setting the <code>method</code> property of the mock request:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(PersonController) |
| class PersonControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test save'() { |
| when: |
| request.method = 'POST' |
| controller.save()<p class="paragraph"/> then: |
| // … |
| } |
| }</pre></div><p class="paragraph"/>This is particularly important if your actions do different things depending on the type of the request. Finally, you can mark a request as AJAX like so:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(PersonController) |
| class PersonControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test list'() { |
| when: |
| request.method = 'POST' |
| request.makeAjaxRequest() |
| controller.getPage()<p class="paragraph"/> then: |
| // … |
| } |
| }</pre></div><p class="paragraph"/>You only need to do this though if the code under test uses the <code>xhr</code> property on the request.<p class="paragraph"/><h4>Testing View Rendering</h4><p class="paragraph"/>To test view rendering you can inspect the state of the controller's <code>modelAndView</code> property (an instance of <code>org.springframework.web.servlet.ModelAndView</code>) or you can use the <code>view</code> and <code>model</code> properties provided by the mixin:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| def home() { |
| render view: <span class="java-quote">"homePage"</span>, model: [title: <span class="java-quote">"Hello World"</span>] |
| } |
| … |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test home'() { |
| when: |
| controller.home()<p class="paragraph"/> then: |
| view == '/simple/homePage' |
| model.title == 'Hello World' |
| } |
| }</pre></div><p class="paragraph"/>Note that the view string is the absolute view path, so it starts with a '/' and will include path elements, such as the directory named after the action's controller.<p class="paragraph"/><h4>Testing Template Rendering</h4><p class="paragraph"/>Unlike view rendering, template rendering will actually attempt to write the template directly to the response rather than returning a <code>ModelAndView</code> hence it requires a different approach to testing.<p class="paragraph"/>Consider the following controller action:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| def display() { |
| render template:<span class="java-quote">"snippet"</span> |
| } |
| }</pre></div><p class="paragraph"/>In this example the controller will look for a template in <code>grails-app/views/simple/_snippet.gsp</code>. You can test this as follows:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test display'() { |
| when: |
| controller.display()<p class="paragraph"/> then: |
| response.text == 'contents of the template' |
| } |
| }</pre></div><p class="paragraph"/>However, you may not want to render the real template, but just test that is was rendered. In this case you can provide mock Groovy Pages:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test display with mock template'() { |
| when: |
| views['/simple/_snippet.gsp'] = 'mock template contents' |
| controller.display()<p class="paragraph"/> then: |
| response.text == 'mock template contents' |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing Actions Which Return A Map</h4><p class="paragraph"/>When a controller action returns a <code>java.util.Map</code> that <code>Map</code> may be inspected directly to assert that it contains the expected data:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| def showBookDetails() { |
| [title: 'The Nature Of Necessity', author: 'Alvin Plantinga'] |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test show book details'() { |
| when: |
| def model = controller.showBookDetails()<p class="paragraph"/> then: |
| model.author == 'Alvin Plantinga' |
| } |
| }</pre></div><p class="paragraph"/> |
| <h4>Testing XML and JSON Responses</h4><p class="paragraph"/>XML and JSON response are also written directly to the response. Grails' mocking capabilities provide some conveniences for testing XML and JSON response. For example consider the following action:<p class="paragraph"/><div class="code"><pre>def renderXml() { |
| render(contentType:<span class="java-quote">"text/xml"</span>) { |
| book(title:<span class="java-quote">"Great"</span>) |
| } |
| }</pre></div><p class="paragraph"/>This can be tested using the <code>xml</code> property of the response:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test render xml'() { |
| when: |
| controller.renderXml()<p class="paragraph"/> then: |
| response.text == <span class="java-quote">"<book title='Great'/>"</span> |
| response.xml.@title.text() == 'Great' |
| } |
| }</pre></div><p class="paragraph"/>The <code>xml</code> property is a parsed result from Groovy's <a href="http://groovy.codehaus.org/Reading+XML+using+Groovy's+XmlSlurper" target="blank">XmlSlurper</a> class which is very convenient for parsing XML.<p class="paragraph"/>Testing JSON responses is pretty similar, instead you use the <code>json</code> property:<p class="paragraph"/><div class="code"><pre>// controller action |
| def renderJson() { |
| render(contentType:<span class="java-quote">"application/json"</span>) { |
| book = <span class="java-quote">"Great"</span> |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test render json'() { |
| when: |
| controller.renderJson()<p class="paragraph"/> then: |
| response.text == '{<span class="java-quote">"book"</span>:<span class="java-quote">"Great"</span>}' |
| response.json.book == 'Great' |
| } |
| }</pre></div><p class="paragraph"/>The <code>json</code> property is an instance of <code>org.codehaus.groovy.grails.web.json.JSONElement</code> which is a map-like structure that is useful for parsing JSON responses.<p class="paragraph"/><h4>Testing XML and JSON Requests</h4><p class="paragraph"/>Grails provides various convenient ways to automatically parse incoming XML and JSON packets. For example you can bind incoming JSON or XML requests using Grails' data binding:<p class="paragraph"/><div class="code"><pre>def consumeBook(Book b) { |
| render <span class="java-quote">"The title is ${b.title}."</span> |
| }</pre></div><p class="paragraph"/>To test this Grails provides an easy way to specify an XML or JSON packet via the <code>xml</code> or <code>json</code> properties. For example the above action can be tested by specifying a String containing the XML:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock([Book]) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification { |
| void 'test consume book xml'() { |
| when: |
| request.xml = '<book><title>Wool</title></book>' |
| controller.consumeBook()<p class="paragraph"/> then: |
| response.text == 'The title is Wool.' |
| } |
| }</pre></div><p class="paragraph"/>Or alternatively a domain instance can be specified and it will be auto-converted into the appropriate XML request:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock([Book]) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test consume book xml'() { |
| when: |
| request.xml = <span class="java-keyword">new</span> Book(title: 'Shift') |
| controller.consumeBook()<p class="paragraph"/> then: |
| response.text == 'The title is Shift.' |
| } |
| }</pre></div><p class="paragraph"/>The same can be done for JSON requests:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock([Book]) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test consume book json'() { |
| when: |
| request.json = <span class="java-keyword">new</span> Book(title: 'Shift') |
| controller.consumeBook()<p class="paragraph"/> then: |
| response.text == 'The title is Shift.' |
| } |
| }</pre></div><p class="paragraph"/>If you prefer not to use Grails' data binding but instead manually parse the incoming XML or JSON that can be tested too. For example consider the controller action below:<p class="paragraph"/><div class="code"><pre>def consume() { |
| request.withFormat { |
| xml { |
| render <span class="java-quote">"The XML Title Is ${request.XML.@title}."</span> |
| } |
| json { |
| render <span class="java-quote">"The JSON Title Is ${request.JSON.title}."</span> |
| } |
| } |
| }</pre></div><p class="paragraph"/>To test the XML request you can specify the XML as a string:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test consume xml'() { |
| when: |
| request.xml = '<book title=<span class="java-quote">"The Stand"</span>/>' |
| controller.consume()<p class="paragraph"/> then: |
| response.text == 'The XML Title Is The Stand.' |
| }<p class="paragraph"/> void 'test consume json'() { |
| when: |
| request.json = '{title:<span class="java-quote">"The Stand"</span>}' |
| controller.consume()<p class="paragraph"/> then: |
| response.text == 'The JSON Title Is The Stand.' |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing Mime Type Handling</h4><p class="paragraph"/>You can test mime type handling and the <code>withFormat</code> method quite simply by setting the request's <code>contentType</code> attribute:<p class="paragraph"/><div class="code"><pre>// controller action |
| def sayHello() { |
| def data = [Hello:<span class="java-quote">"World"</span>] |
| request.withFormat { |
| xml { render data as grails.converters.XML } |
| json { render data as grails.converters.JSON } |
| html data |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test say hello xml'() { |
| when: |
| request.contentType = 'application/xml' |
| controller.sayHello()<p class="paragraph"/> then: |
| response.text == '<?xml version=<span class="java-quote">"1.0"</span> encoding=<span class="java-quote">"UTF-8"</span>?><map><entry key=<span class="java-quote">"Hello"</span>>World</entry></map>' |
| }<p class="paragraph"/> void 'test say hello json'() { |
| when: |
| request.contentType = 'application/json' |
| controller.sayHello()<p class="paragraph"/> then: |
| response.text == '{<span class="java-quote">"Hello"</span>:<span class="java-quote">"World"</span>}' |
| } |
| }</pre></div><p class="paragraph"/>There are constants provided by <code>ControllerUnitTestMixin</code> for all of the common common content types as shown below:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test say hello xml'() { |
| when: |
| request.contentType = XML_CONTENT_TYPE |
| controller.sayHello()<p class="paragraph"/> then: |
| response.text == '<?xml version=<span class="java-quote">"1.0"</span> encoding=<span class="java-quote">"UTF-8"</span>?><map><entry key=<span class="java-quote">"Hello"</span>>World</entry></map>' |
| }<p class="paragraph"/> void 'test say hello json'() { |
| when: |
| request.contentType = JSON_CONTENT_TYPE |
| controller.sayHello()<p class="paragraph"/> then: |
| response.text == '{<span class="java-quote">"Hello"</span>:<span class="java-quote">"World"</span>}' |
| } |
| }</pre></div><p class="paragraph"/>The defined constants are listed below:<p class="paragraph"/><table class="wiki-table" cellpadding="0" cellspacing="0" border="0"><tr><th><strong class="bold">Constant</strong></th><th><strong class="bold">Value</strong></th></tr><tr class="table-odd"><td>ALL_CONTENT_TYPE</td><td>*/*</td></tr><tr class="table-even"><td>FORM_CONTENT_TYPE</td><td>application/x-www-form-urlencoded</td></tr><tr class="table-odd"><td>MULTIPART_FORM_CONTENT_TYPE</td><td>multipart/form-data</td></tr><tr class="table-even"><td>HTML_CONTENT_TYPE</td><td>text/html</td></tr><tr class="table-odd"><td>XHTML_CONTENT_TYPE</td><td>application/xhtml+xml</td></tr><tr class="table-even"><td>XML_CONTENT_TYPE</td><td>application/xml</td></tr><tr class="table-odd"><td>JSON_CONTENT_TYPE</td><td>application/json</td></tr><tr class="table-even"><td>TEXT_XML_CONTENT_TYPE</td><td>text/xml</td></tr><tr class="table-odd"><td>TEXT_JSON_CONTENT_TYPE</td><td>text/json</td></tr><tr class="table-even"><td>HAL_JSON_CONTENT_TYPE</td><td>application/hal+json</td></tr><tr class="table-odd"><td>HAL_XML_CONTENT_TYPE</td><td>application/hal+xml</td></tr><tr class="table-even"><td>ATOM_XML_CONTENT_TYPE</td><td>application/atom+xml</td></tr></table><p class="paragraph"/><h4>Testing Duplicate Form Submissions</h4><p class="paragraph"/>Testing duplicate form submissions is a little bit more involved. For example if you have an action that handles a form such as:<p class="paragraph"/><div class="code"><pre>def handleForm() { |
| withForm { |
| render <span class="java-quote">"Good"</span> |
| }.invalidToken { |
| render <span class="java-quote">"Bad"</span> |
| } |
| }</pre></div><p class="paragraph"/>you want to verify the logic that is executed on a good form submission and the logic that is executed on a duplicate submission. Testing the bad submission is simple. Just invoke the controller:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test duplicate form submission'() { |
| when: |
| controller.handleForm()<p class="paragraph"/> then: |
| response.text == 'Bad' |
| } |
| }</pre></div><p class="paragraph"/>Testing the successful submission requires providing an appropriate <code>SynchronizerToken</code>:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/><span class="java-keyword">import</span> org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerTokensHolder<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test valid form submission'() { |
| when: |
| def tokenHolder = SynchronizerTokensHolder.store(session)<p class="paragraph"/> params[SynchronizerTokensHolder.TOKEN_URI] = '/controller/handleForm' |
| params[SynchronizerTokensHolder.TOKEN_KEY] = tokenHolder.generateToken(params[SynchronizerTokensHolder.TOKEN_URI]) |
| controller.handleForm()<p class="paragraph"/> then: |
| response.text == 'Good' |
| } |
| }</pre></div><p class="paragraph"/>If you test both the valid and the invalid request in the same test be sure to reset the response between executions of the controller:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/><span class="java-keyword">import</span> org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerTokensHolder<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test form submission'() { |
| when: |
| controller.handleForm()<p class="paragraph"/> then: |
| response.text == 'Bad'<p class="paragraph"/> when: |
| response.reset() |
| def tokenHolder = SynchronizerTokensHolder.store(session)<p class="paragraph"/> params[SynchronizerTokensHolder.TOKEN_URI] = '/controller/handleForm' |
| params[SynchronizerTokensHolder.TOKEN_KEY] = tokenHolder.generateToken(params[SynchronizerTokensHolder.TOKEN_URI]) |
| controller.handleForm()<p class="paragraph"/> then: |
| response.text == 'Good' |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing File Upload</h4><p class="paragraph"/>You use the <code>GrailsMockMultipartFile</code> class to test file uploads. For example consider the following controller action:<p class="paragraph"/><div class="code"><pre>def uploadFile() { |
| MultipartFile file = request.getFile(<span class="java-quote">"myFile"</span>) |
| file.transferTo(<span class="java-keyword">new</span> File(<span class="java-quote">"/local/disk/myFile"</span>)) |
| }</pre></div><p class="paragraph"/>To test this action you can register a <code>GrailsMockMultipartFile</code> with the request:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/><span class="java-keyword">import</span> org.codehaus.groovy.grails.plugins.testing.GrailsMockMultipartFile<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test file upload'() { |
| when: |
| def file = <span class="java-keyword">new</span> GrailsMockMultipartFile('myFile', 'some file contents'.bytes) |
| request.addFile file |
| controller.uploadFile()<p class="paragraph"/> then: |
| file.targetFileLocation.path == '/local/disk/myFile' |
| } |
| }</pre></div><p class="paragraph"/>The <code>GrailsMockMultipartFile</code> constructor arguments are the name and contents of the file. It has a mock implementation of the <code>transferTo</code> method that simply records the <code>targetFileLocation</code> and doesn't write to disk.<p class="paragraph"/><h4>Testing Command Objects</h4><p class="paragraph"/>Special support exists for testing command object handling with the <code>mockCommandObject</code> method. For example consider the following action:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| def handleCommand(SimpleCommand simple) { |
| <span class="java-keyword">if</span>(simple.hasErrors()) { |
| render 'Bad' |
| } <span class="java-keyword">else</span> { |
| render 'Good' |
| } |
| } |
| }<p class="paragraph"/>class SimpleCommand { |
| <span class="java-object">String</span> name<p class="paragraph"/> <span class="java-keyword">static</span> constraints = { |
| name blank: <span class="java-keyword">false</span> |
| } |
| }</pre></div><p class="paragraph"/>To test this you mock the command object, populate it and then validate it as follows:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test valid command object'() { |
| given: |
| def simpleCommand = <span class="java-keyword">new</span> SimpleCommand(name: 'Hugh') |
| simpleCommand.validate()<p class="paragraph"/> when: |
| controller.handleCommand(simpleCommand)<p class="paragraph"/> then: |
| response.text == 'Good' |
| }<p class="paragraph"/> void 'test invalid command object'() { |
| given: |
| def simpleCommand = <span class="java-keyword">new</span> SimpleCommand(name: '') |
| simpleCommand.validate()<p class="paragraph"/> when: |
| controller.handleCommand(simpleCommand)<p class="paragraph"/> then: |
| response.text == 'Bad' |
| } |
| }</pre></div><p class="paragraph"/>The testing framework also supports allowing Grails to create the command object instance automatically. To test this invoke the no-arg version of the controller action method. Grails will create an instance of the command object, perform data binding on it using the request parameters and validate the object just like it does in when the application is running. See the test below.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test valid command object'() { |
| when: |
| params.name = 'Hugh' |
| controller.handleCommand()<p class="paragraph"/> then: |
| response.text == 'Good' |
| }<p class="paragraph"/> void 'test invalid command object'() { |
| when: |
| params.name = '' |
| controller.handleCommand()<p class="paragraph"/> then: |
| response.text == 'Bad' |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing allowedMethods</h4><p class="paragraph"/>The unit testing environment respects the <a href="../ref/Controllers/allowedMethods.html" class="controllers">allowedMethods</a> property in controllers. If a controller action is limited to be accessed with certain request methods, the unit test must be constructed to deal with that.<p class="paragraph"/><div class="code"><pre>// grails-app/controllers/com/demo/DemoController.groovypackage com.demo<p class="paragraph"/>class DemoController {<p class="paragraph"/> <span class="java-keyword">static</span> allowedMethods = [save: 'POST', update: 'PUT', delete: 'DELETE']<p class="paragraph"/> def save() { |
| render 'Save was successful!' |
| }<p class="paragraph"/> // … |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test/unit/com/demo/DemoControllerSpec.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> <span class="java-keyword">static</span> javax.servlet.http.HttpServletResponse.*<p class="paragraph"/>@TestFor(DemoController) |
| class DemoControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test a valid request method"</span>() { |
| when: |
| request.method = 'POST' |
| controller.save()<p class="paragraph"/> then: |
| response.status == SC_OK |
| response.text == 'Save was successful!' |
| }<p class="paragraph"/> void <span class="java-quote">"test an invalid request method"</span>() { |
| when: |
| request.method = 'DELETE' |
| controller.save()<p class="paragraph"/> then: |
| response.status == SC_METHOD_NOT_ALLOWED |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing Calling Tag Libraries</h4><p class="paragraph"/>You can test calling tag libraries using <code>ControllerUnitTestMixin</code>, although the mechanism for testing the tag called varies from tag to tag. For example to test a call to the <code>message</code> tag, add a message to the <code>messageSource</code>. Consider the following action:<p class="paragraph"/><div class="code"><pre>def showMessage() { |
| render g.message(code: <span class="java-quote">"foo.bar"</span>) |
| }</pre></div><p class="paragraph"/>This can be tested as follows:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'test render message tag'() { |
| given: |
| messageSource.addMessage 'foo.bar', request.locale, 'Hello World'<p class="paragraph"/> when: |
| controller.showMessage()<p class="paragraph"/> then: |
| response.text == 'Hello World' |
| } |
| }</pre></div><p class="paragraph"/>See <a href="../guide/single.html#unitTestingTagLibraries" class="guide">unit testing tag libraries</a> for more information. |
| |
| |
| <a name="12.1.2 Unit Testing Tag Libraries"><!-- Legacy link --></a> |
| <h2 id="unitTestingTagLibraries">14.1.2 Unit Testing Tag Libraries</h2> |
| <h4>The Basics</h4><p class="paragraph"/>Tag libraries and GSP pages can be tested with the <code>grails.test.mixin.web.GroovyPageUnitTestMixin</code> mixin. To use the mixin declare which tag library is under test with the <code>TestFor</code> annotation:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleTagLib) |
| class SimpleTagLibSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| } |
| }</pre></div><p class="paragraph"/>Adding the <code>TestFor</code> annotation to a TagLib class causes a new <code>tagLib</code> field to be automatically created for the TagLib class under test. |
| The tagLib field can be used to test calling tags as function calls. The return value of a function call is either a <a href="https://grails.apache.org/docs/3.0.x/api/org/grails/buffer/StreamCharBuffer.html" class="api">StreamCharBuffer</a> instance or |
| the object returned from the tag closure when <a href="../guide/single.html#tagReturnValue" class="guide">returnObjectForTags</a> feature is used.<p class="paragraph"/>Note that if you are testing invocation of a custom tag from a controller you can combine the <code>ControllerUnitTestMixin</code> and the <code>GroovyPageUnitTestMixin</code> using the <code>Mock</code> annotation:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock(SimpleTagLib) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/>}</pre></div><p class="paragraph"/><h4>Testing Custom Tags</h4><p class="paragraph"/>The core Grails tags don't need to be enabled during testing, however custom tag libraries do. The <code>GroovyPageUnitTestMixin</code> class provides a <code>mockTagLib()</code> method that you can use to mock a custom tag library. For example consider the following tag library:<p class="paragraph"/><div class="code"><pre>class SimpleTagLib {<p class="paragraph"/> <span class="java-keyword">static</span> namespace = 's'<p class="paragraph"/> def hello = { attrs, body -> |
| out << <span class="java-quote">"Hello ${attrs.name ?: 'World'}"</span> |
| }<p class="paragraph"/> def bye = { attrs, body -> |
| out << <span class="java-quote">"Bye ${attrs.author.name ?: 'World'}"</span> |
| } |
| }</pre></div><p class="paragraph"/>You can test this tag library by using <code>TestFor</code> and supplying the name of the tag library:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleTagLib) |
| class SimpleTagLibSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test hello tag"</span>() { |
| expect: |
| applyTemplate('<s:hello />') == 'Hello World' |
| applyTemplate('<s:hello name=<span class="java-quote">"Fred"</span> />') == 'Hello Fred' |
| applyTemplate('<s:bye author=<span class="java-quote">"${author}"</span> />', [author: <span class="java-keyword">new</span> Author(name: 'Fred')]) == 'Bye Fred' |
| }<p class="paragraph"/> void <span class="java-quote">"test tag calls"</span>() { |
| expect: |
| tagLib.hello().toString() == 'Hello World' |
| tagLib.hello(name: 'Fred').toString() == 'Hello Fred' |
| tagLib.bye(author: <span class="java-keyword">new</span> Author(name: 'Fred')).toString == 'Bye Fred' |
| } |
| }</pre></div><p class="paragraph"/>Alternatively, you can use the <code>TestMixin</code> annotation and mock multiple tag libraries using the <code>mockTagLib()</code> method:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> grails.test.mixin.web.GroovyPageUnitTestMixin<p class="paragraph"/>@TestMixin(GroovyPageUnitTestMixin) |
| class MultipleTagLibSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test multiple tags"</span>() { |
| given: |
| mockTagLib(SomeTagLib) |
| mockTagLib(SomeOtherTagLib)<p class="paragraph"/> expect: |
| // … |
| } |
| }</pre></div><p class="paragraph"/>The <code>GroovyPageUnitTestMixin</code> provides convenience methods for asserting that the template output equals or matches an expected value.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleTagLib) |
| class SimpleTagLibSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test hello tag"</span>() { |
| expect: |
| assertOutputEquals ('Hello World', '<s:hello />') |
| assertOutputMatches (/.*Fred.*/, '<s:hello name=<span class="java-quote">"Fred"</span> />') |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing View and Template Rendering</h4><p class="paragraph"/>You can test rendering of views and templates in <code>grails-app/views</code> via the <code>render(Map)</code> method provided by <code>GroovyPageUnitTestMixin</code> :<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> grails.test.mixin.web.GroovyPageUnitTestMixin<p class="paragraph"/>@TestMixin(GroovyPageUnitTestMixin) |
| class RenderingSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test rendering template"</span>() { |
| when: |
| def result = render(template: '/simple/hello')<p class="paragraph"/> then: |
| result == 'Hello World!' |
| } |
| }</pre></div><p class="paragraph"/>This will attempt to render a template found at the location <code>grails-app/views/simple/_hello.gsp</code>. Note that if the template depends on any custom tag libraries you need to call <code>mockTagLib</code> as described in the previous section.<p class="paragraph"/>Some core tags use the active controller and action as input. In GroovyPageUnitTestMixin tests, you can manually set the active controller and action name by setting controllerName and actionName properties on the webRequest object:<p class="paragraph"/><div class="code"><pre>webRequest.controllerName = 'simple' |
| webRequest.actionName = 'hello'</pre></div> |
| |
| |
| <a name="12.1.3 Unit Testing Domains"><!-- Legacy link --></a> |
| <h2 id="unitTestingDomains">14.1.3 Unit Testing Domains</h2> |
| <h4>Overview</h4><p class="paragraph"/>Domain class interaction can be tested without involving a real database connection using <code>DomainClassUnitTestMixin</code> or by using the <code>HibernateTestMixin</code>.<p class="paragraph"/>The GORM implementation in DomainClassUnitTestMixin is using a simple in-memory <code>ConcurrentHashMap</code> implementation. Note that this has limitations compared to a real GORM implementation.<p class="paragraph"/>A large, commonly-used portion of the GORM API can be mocked using <code>DomainClassUnitTestMixin</code> including: |
| <ul class="star"> |
| <li>Simple persistence methods like <code>save()</code>, <code>delete()</code> etc.</li> |
| <li>Dynamic Finders</li> |
| <li>Named Queries</li> |
| <li>Query-by-example</li> |
| <li>GORM Events</li> |
| </ul><p class="paragraph"/><code>HibernateTestMixin</code> uses Hibernate 4 and a H2 in-memory database. This makes it possible to use all GORM features also in Grails unit tests.<p class="paragraph"/>All features of GORM for Hibernate can be tested within a <code>HibernateTestMixin</code> unit test including: |
| <ul class="star"> |
| <li>String-based HQL queries</li> |
| <li>composite identifiers</li> |
| <li>dirty checking methods</li> |
| <li>any direct interaction with Hibernate</li> |
| </ul><p class="paragraph"/>The implementation behind <code>HibernateTestMixin</code> takes care of setting up the Hibernate with the in-memory H2 database. It only configures the given domain classes for use in a unit test. The @Domain annotation is used to tell which domain classes should be configured.<p class="paragraph"/> |
| <h4>DomainClassUnitTestMixin Basics</h4><p class="paragraph"/><code>DomainClassUnitTestMixin</code> is typically used in combination with testing either a controller, service or tag library where the domain is a mock collaborator defined by the <code>Mock</code> annotation:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(BookController) |
| @Mock(Book) |
| class BookControllerSpec <span class="java-keyword">extends</span> Specification { |
| // … |
| }</pre></div><p class="paragraph"/>The example above tests the <code>SimpleController</code> class and mocks the behavior of the <code>Simple</code> domain class as well. For example consider a typical scaffolded <code>save</code> controller action:<p class="paragraph"/><div class="code"><pre>class BookController { |
| def save() { |
| def book = <span class="java-keyword">new</span> Book(params) |
| <span class="java-keyword">if</span> (book.save(flush: <span class="java-keyword">true</span>)) { |
| flash.message = message( |
| code: '<span class="java-keyword">default</span>.created.message', |
| args: [message(code: 'book.label', <span class="java-keyword">default</span>: 'Book'), book.id]) |
| redirect(action: <span class="java-quote">"show"</span>, id: book.id) |
| } |
| <span class="java-keyword">else</span> { |
| render(view: <span class="java-quote">"create"</span>, model: [bookInstance: book]) |
| } |
| } |
| }</pre></div><p class="paragraph"/>Tests for this action can be written as follows:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(BookController) |
| @Mock(Book) |
| class BookControllerSpec <span class="java-keyword">extends</span> Specification { |
| void <span class="java-quote">"test saving an invalid book"</span>() { |
| when: |
| controller.save()<p class="paragraph"/> then: |
| model.bookInstance != <span class="java-keyword">null</span> |
| view == '/book/create' |
| }<p class="paragraph"/> void <span class="java-quote">"test saving a valid book"</span>() { |
| when: |
| params.title = <span class="java-quote">"The Stand"</span> |
| params.pages = <span class="java-quote">"500"</span><p class="paragraph"/> controller.save()<p class="paragraph"/> then: |
| response.redirectedUrl == '/book/show/1' |
| flash.message != <span class="java-keyword">null</span> |
| Book.count() == 1 |
| } |
| }</pre></div><p class="paragraph"/><code>Mock</code> annotation also supports a list of mock collaborators if you have more than one domain to mock:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(BookController) |
| @Mock([Book, Author]) |
| class BookControllerSpec <span class="java-keyword">extends</span> Specification { |
| // … |
| }</pre></div><p class="paragraph"/>Alternatively you can also use the <code>DomainClassUnitTestMixin</code> directly with the <code>TestMixin</code> annotation and then call the <code>mockDomain</code> method to mock domains during your test:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> spock.lang.Specification |
| <span class="java-keyword">import</span> grails.test.mixin.domain.DomainClassUnitTestMixin<p class="paragraph"/>@TestFor(BookController) |
| @TestMixin(DomainClassUnitTestMixin) |
| class BookControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void setupSpec() { |
| mockDomain(Book) |
| }<p class="paragraph"/> void <span class="java-quote">"test saving an invalid book"</span>() { |
| when: |
| controller.save()<p class="paragraph"/> then: |
| model.bookInstance != <span class="java-keyword">null</span> |
| view == '/book/create' |
| }<p class="paragraph"/> void <span class="java-quote">"test saving a valid book"</span>() { |
| when: |
| params.title = <span class="java-quote">"The Stand"</span> |
| params.pages = <span class="java-quote">"500"</span><p class="paragraph"/> controller.save()<p class="paragraph"/> then: |
| response.redirectedUrl == '/book/show/1' |
| flash.message != <span class="java-keyword">null</span> |
| Book.count() == 1 |
| } |
| }</pre></div><p class="paragraph"/>The <code>mockDomain</code> method also includes an additional parameter that lets you pass a Map of Maps to configure a domain, which is useful for fixture-like data:<p class="paragraph"/><div class="code"><pre>mockDomain(Book, [ |
| [title: <span class="java-quote">"The Stand"</span>, pages: 1000], |
| [title: <span class="java-quote">"The Shining"</span>, pages: 400], |
| [title: <span class="java-quote">"Along Came a Spider"</span>, pages: 300] ])</pre></div><p class="paragraph"/><h4>Testing Constraints</h4><p class="paragraph"/>There are 3 types of validateable classes: |
| <ol> |
| <li>Domain classes</li> |
| <li>Classes which implement the <code>Validateable</code> trait</li> |
| <li>Command Objects which have been made validateable automatically</li> |
| </ol><p class="paragraph"/>These are all easily testable in a unit test with no special configuration necessary as long as the test method is marked with <code>TestFor</code> or explicitly applies the <code>GrailsUnitTestMixin</code> using <code>TestMixin</code>. See the examples below.<p class="paragraph"/><div class="code"><pre>// src/groovy/com/demo/MyValidateable.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/>class MyValidateable <span class="java-keyword">implements</span> grails.validation.Validateable { |
| <span class="java-object">String</span> name |
| <span class="java-object">Integer</span> age<p class="paragraph"/> <span class="java-keyword">static</span> constraints = { |
| name matches: /[A-Z].*/ |
| age range: 1..99 |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// grails-app/domain/com/demo/Person.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/>class Person { |
| <span class="java-object">String</span> name<p class="paragraph"/> <span class="java-keyword">static</span> constraints = { |
| name matches: /[A-Z].*/ |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// grails-app/controllers/com/demo/DemoController.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/>class DemoController {<p class="paragraph"/> def addItems(MyCommandObject co) { |
| <span class="java-keyword">if</span>(co.hasErrors()) { |
| render 'something went wrong' |
| } <span class="java-keyword">else</span> { |
| render 'items have been added' |
| } |
| } |
| }<p class="paragraph"/>class MyCommandObject { |
| <span class="java-object">Integer</span> numberOfItems<p class="paragraph"/> <span class="java-keyword">static</span> constraints = { |
| numberOfItems range: 1..10 |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test/unit/com/demo/PersonSpec.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(Person) |
| class PersonSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"Test that name must begin with an upper <span class="java-keyword">case</span> letter"</span>() { |
| when: 'the name begins with a lower letter' |
| def p = <span class="java-keyword">new</span> Person(name: 'jeff')<p class="paragraph"/> then: 'validation should fail' |
| !p.validate()<p class="paragraph"/> when: 'the name begins with an upper <span class="java-keyword">case</span> letter' |
| p = <span class="java-keyword">new</span> Person(name: 'Jeff')<p class="paragraph"/> then: 'validation should pass' |
| p.validate() |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test/unit/com/demo/DemoControllerSpec.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(DemoController) |
| class DemoControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'Test an invalid number of items'() { |
| when: |
| params.numberOfItems = 42 |
| controller.addItems()<p class="paragraph"/> then: |
| response.text == 'something went wrong' |
| }<p class="paragraph"/> void 'Test a valid number of items'() { |
| when: |
| params.numberOfItems = 8 |
| controller.addItems()<p class="paragraph"/> then: |
| response.text == 'items have been added' |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test/unit/com/demo/MyValidateableSpec.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/> |
| @TestMixin(GrailsUnitTestMixin) |
| class MyValidateableSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'Test validate can be invoked in a unit test with no special configuration'() { |
| when: 'an object is valid' |
| def validateable = <span class="java-keyword">new</span> MyValidateable(name: 'Kirk', age: 47)<p class="paragraph"/> then: 'validate() returns <span class="java-keyword">true</span> and there are no errors' |
| validateable.validate() |
| !validateable.hasErrors() |
| validateable.errors.errorCount == 0<p class="paragraph"/> when: 'an object is invalid' |
| validateable.name = 'kirk'<p class="paragraph"/> then: 'validate() returns <span class="java-keyword">false</span> and the appropriate error is created' |
| !validateable.validate() |
| validateable.hasErrors() |
| validateable.errors.errorCount == 1 |
| validateable.errors['name'].code == 'matches.invalid'<p class="paragraph"/> when: 'the clearErrors() is called' |
| validateable.clearErrors()<p class="paragraph"/> then: 'the errors are gone' |
| !validateable.hasErrors() |
| validateable.errors.errorCount == 0<p class="paragraph"/> when: 'the object is put back in a valid state' |
| validateable.name = 'Kirk'<p class="paragraph"/> then: 'validate() returns <span class="java-keyword">true</span> and there are no errors' |
| validateable.validate() |
| !validateable.hasErrors() |
| validateable.errors.errorCount == 0 |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test/unit/com/demo/MyCommandObjectSpec.groovy |
| <span class="java-keyword">package</span> com.demo<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> grails.test.mixin.support.GrailsUnitTestMixin |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestMixin(GrailsUnitTestMixin) |
| class MyCommandObjectSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void 'Test that numberOfItems must be between 1 and 10'() { |
| when: 'numberOfItems is less than 1' |
| def co = <span class="java-keyword">new</span> MyCommandObject() |
| co.numberOfItems = 0<p class="paragraph"/> then: 'validation fails' |
| !co.validate() |
| co.hasErrors() |
| co.errors['numberOfItems'].code == 'range.toosmall'<p class="paragraph"/> when: 'numberOfItems is greater than 10' |
| co.numberOfItems = 11<p class="paragraph"/> then: 'validation fails' |
| !co.validate() |
| co.hasErrors() |
| co.errors['numberOfItems'].code == 'range.toobig'<p class="paragraph"/> when: 'numberOfItems is greater than 1' |
| co.numberOfItems = 1<p class="paragraph"/> then: 'validation succeeds' |
| co.validate() |
| !co.hasErrors()<p class="paragraph"/> when: 'numberOfItems is greater than 10' |
| co.numberOfItems = 10<p class="paragraph"/> then: 'validation succeeds' |
| co.validate() |
| !co.hasErrors() |
| } |
| }</pre></div><p class="paragraph"/>That's it for testing constraints. One final thing we would like to say is that testing the constraints in this way catches a common error: typos in the "constraints" property name which is a mistake that is easy to make and equally easy to overlook. A unit test for your constraints will highlight the problem straight away.<p class="paragraph"/> |
| <h4>HibernateTestMixin Basics</h4><p class="paragraph"/><code>HibernateTestMixin</code> allows Hibernate 4 to be used in Grails unit tests. It uses a H2 in-memory database.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestMixin |
| <span class="java-keyword">import</span> grails.test.mixin.gorm.Domain |
| <span class="java-keyword">import</span> grails.test.mixin.hibernate.HibernateTestMixin |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/> |
| @Domain(Person) |
| @TestMixin(HibernateTestMixin) |
| class PersonSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"Test count people"</span>() { |
| expect: <span class="java-quote">"Test execute Hibernate count query"</span> |
| Person.count() == 0 |
| sessionFactory != <span class="java-keyword">null</span> |
| transactionManager != <span class="java-keyword">null</span> |
| hibernateSession != <span class="java-keyword">null</span> |
| } |
| }</pre></div><p class="paragraph"/>This library dependency is required in build.gradle for adding support for <code>HibernateTestMixin</code>.<p class="paragraph"/><div class="code"><pre>dependencies { |
| testCompile 'org.grails:grails-datastore-test-support:4.0.4.RELEASE' |
| }</pre></div><p class="paragraph"/>HibernateTestMixin is only supported with hibernate4 plugin versions >= 4.3.8.1 . |
| <div class="code"><pre>dependencies { |
| compile <span class="java-quote">"org.grails.plugins:hibernate:4.3.8.1"</span> |
| }</pre></div><p class="paragraph"/> |
| <h4>Configuring domain classes for HibernateTestMixin tests</h4><p class="paragraph"/>The <code>grails.test.mixin.gorm.Domain</code> annotation is used to configure the list of domain classes to configure for Hibernate sessionFactory instance that gets configured when the unit test runtime is initialized.<p class="paragraph"/><code>Domain</code> annotations will be collected from several locations: |
| <ul class="star"> |
| <li>the annotations on the test class</li> |
| <li>the package annotations in the package-info.java/package-info.groovy file in the package of the test class</li> |
| <li>each super class of the test class and their respective package annotations</li> |
| <li>the possible <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/SharedRuntime.html" class="api">SharedRuntime</a></code> class</li> |
| </ul><p class="paragraph"/><code>Domain</code> annotations can be shared by adding them as package annotations to package-info.java/package-info.groovy files or by adding them to a <code><a href="https://grails.apache.org/docs/3.0.x/api/grails/test/runtime/SharedRuntime.html" class="api">SharedRuntime</a></code> class which has been added for the test.<p class="paragraph"/>It's not possible to use DomainClassUnitTestMixin's <code>Mock</code> annotation in HibernateTestMixin tests. Use the <code>Domain</code> annotation in the place of <code>Mock</code> in HibernateTestMixin tests. |
| |
| <a name="12.1.4 Unit Testing Filters"><!-- Legacy link --></a> |
| <h2 id="unitTestingFilters">14.1.4 Unit Testing Filters</h2> |
| Unit testing filters is typically a matter of testing a controller where a filter is a mock collaborator. For example consider the following filters class:<p class="paragraph"/><div class="code"><pre>class CancellingFilters { |
| def filters = { |
| all(controller:<span class="java-quote">"simple"</span>, action:<span class="java-quote">"list"</span>) { |
| before = { |
| redirect(controller:<span class="java-quote">"book"</span>) |
| <span class="java-keyword">return</span> <span class="java-keyword">false</span> |
| } |
| } |
| } |
| }</pre></div><p class="paragraph"/>This filter interceptors the <code>list</code> action of the <code>simple</code> controller and redirects to the <code>book</code> controller. To test this filter you start off with a test that targets the <code>SimpleController</code> class and add the <code>CancellingFilters</code> as a mock collaborator:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock(CancellingFilters) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> // ...<p class="paragraph"/>}</pre></div><p class="paragraph"/>You can then implement a test that uses the <code>withFilters</code> method to wrap the call to an action in filter execution:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock(CancellingFilters) |
| class SimpleControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test list action is filtered"</span>() { |
| when: |
| withFilters(action:<span class="java-quote">"list"</span>) { |
| controller.list() |
| }<p class="paragraph"/> then: |
| response.redirectedUrl == '/book' |
| } |
| }</pre></div><p class="paragraph"/>Note that the <code>action</code> parameter is required because it is unknown what the action to invoke is until the action is actually called. The <code>controller</code> parameter is optional and taken from the controller under test. If it is another controller you are testing then you can specify it:<p class="paragraph"/><div class="code"><pre>withFilters(controller:<span class="java-quote">"book"</span>,action:<span class="java-quote">"list"</span>) { |
| controller.list() |
| }</pre></div> |
| |
| |
| <a name="12.1.5 Unit Testing URL Mappings"><!-- Legacy link --></a> |
| <h2 id="unitTestingURLMappings">14.1.5 Unit Testing URL Mappings</h2> |
| <h4>The Basics</h4><p class="paragraph"/>Testing URL mappings can be done with the <code>TestFor</code> annotation testing a particular URL mappings class. For example to test the default URL mappings you can do the following:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> com.demo.SimpleController |
| <span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(UrlMappings) |
| @Mock(SimpleController) |
| class UrlMappingsSpec <span class="java-keyword">extends</span> Specification { |
| // … |
| }</pre></div><p class="paragraph"/>As you can see, any controller that is the target of a URL mapping that you're testing <em class="italic">must</em> be added to the <code>@Mock</code> annotation.<p class="paragraph"/><blockquote class="note"> |
| Note that since the default <code>UrlMappings</code> class is in the default package your test must also be in the default package |
| </blockquote><p class="paragraph"/>With that done there are a number of useful methods that are defined by the <code>grails.test.mixin.web.UrlMappingsUnitTestMixin</code> for testing URL mappings. These include: |
| <ul class="star"> |
| <li><code>assertForwardUrlMapping</code> - Asserts a URL mapping is forwarded for the given controller class (note that controller will need to be defined as a mock collaborate for this to work)</li> |
| <li><code>assertReverseUrlMapping</code> - Asserts that the given URL is produced when reverse mapping a link to a given controller and action</li> |
| <li><code>assertUrlMapping</code> - Asserts a URL mapping is valid for the given URL. This combines the <code>assertForwardUrlMapping</code> and <code>assertReverseUrlMapping</code> assertions</li> |
| </ul><p class="paragraph"/><h4>Asserting Forward URL Mappings</h4><p class="paragraph"/>You use <code>assertForwardUrlMapping</code> to assert that a given URL maps to a given controller. For example, consider the following URL mappings:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">static</span> mappings = { |
| <span class="java-quote">"/actionOne"</span>(controller: <span class="java-quote">"simple"</span>, action: <span class="java-quote">"action1"</span>) |
| <span class="java-quote">"/actionTwo"</span>(controller: <span class="java-quote">"simple"</span>, action: <span class="java-quote">"action2"</span>) |
| }</pre></div><p class="paragraph"/>The following test can be written to assert these URL mappings:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> com.demo.SimpleController |
| <span class="java-keyword">import</span> grails.test.mixin.TestFor |
| <span class="java-keyword">import</span> grails.test.mixin.Mock |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(UrlMappings) |
| @Mock(SimpleController) |
| class UrlMappingsSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void <span class="java-quote">"test forward mappings"</span>() { |
| expect: |
| assertForwardUrlMapping(<span class="java-quote">"/actionOne"</span>, controller: 'simple', action: <span class="java-quote">"action1"</span>) |
| assertForwardUrlMapping(<span class="java-quote">"/actionTwo"</span>, controller: 'simple', action: <span class="java-quote">"action2"</span>) |
| } |
| }</pre></div><p class="paragraph"/><h4>Assert Reverse URL Mappings</h4><p class="paragraph"/>You use <code>assertReverseUrlMapping</code> to check that correct links are produced for your URL mapping when using the <code>link</code> tag in GSP views. An example test is largely identical to the previous listing except you use <code>assertReverseUrlMapping</code> instead of <code>assertForwardUrlMapping</code>. Note that you can combine these 2 assertions with <code>assertUrlMapping</code>. |
| |
| |
| <a name="12.1.6 Mocking Collaborators"><!-- Legacy link --></a> |
| <h2 id="mockingCollaborators">14.1.6 Mocking Collaborators</h2> |
| The Spock Framework manual has a chapter on <a href="http://spock-framework.readthedocs.org/en/latest/interaction_based_testing.html" target="blank">Interaction Based Testing</a> which also explains mocking collaborators. |
| |
| <a name="12.1.7 Mocking Codecs"><!-- Legacy link --></a> |
| <h2 id="mockingCodecs">14.1.7 Mocking Codecs</h2> |
| The <code>GrailsUnitTestMixin</code> provides a <code>mockCodec</code> method for mocking <a href="../guide/single.html#codecs" class="guide">custom codecs</a> which may be invoked while a unit test is running.<p class="paragraph"/><div class="code"><pre>mockCodec(MyCustomCodec)</pre></div><p class="paragraph"/>Failing to mock a codec which is invoked while a unit test is running may result in a MissingMethodException. |
| |
| |
| <a name="12.1.8 Unit Test Metaprogramming"><!-- Legacy link --></a> |
| <h2 id="unitTestMetaprogramming">14.1.8 Unit Test Metaprogramming</h2> |
| If runtime metaprogramming needs to be done in a unit test it needs to be done early in the process before the unit testing environment is fully initialized. This should be done when the unit test class is being initialized. For a Spock based test this should be done in the <code>setupSpec()</code> method. For a JUnit test this should be done in a method marked with <code>@BeforeClass</code>.<p class="paragraph"/> |
| <div class="code"><pre><span class="java-keyword">package</span> myapp<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.* |
| <span class="java-keyword">import</span> spock.lang.Specification<p class="paragraph"/>@TestFor(SomeController) |
| class SomeControllerSpec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> |
| def setupSpec() { |
| SomeClass.metaClass.someMethod = { -> |
| // <span class="java-keyword">do</span> something here |
| } |
| }<p class="paragraph"/> // … |
| }</pre></div><p class="paragraph"/><div class="code"><pre><span class="java-keyword">package</span> myapp<p class="paragraph"/><span class="java-keyword">import</span> grails.test.mixin.* |
| <span class="java-keyword">import</span> org.junit.*<p class="paragraph"/>@TestFor(SomeController) |
| class SomeControllerTests {<p class="paragraph"/> @BeforeClass |
| <span class="java-keyword">static</span> void metaProgramController() { |
| SomeClass.metaClass.someMethod = { -> |
| // <span class="java-keyword">do</span> something here |
| } |
| }<p class="paragraph"/> // ...<p class="paragraph"/>}</pre></div> |
| |
| |
| <a name="12.2 Integration Testing"><!-- Legacy link --></a> |
| <h2 id="integrationTesting">14.2 Integration Testing</h2> |
| Integration tests differ from unit tests in that you have full access to the Grails environment within the test. You can create an integration test using the <a href="../ref/Command Line/create-integration-test.html" class="commandLine">create-integration-test</a> command:<p class="paragraph"/><div class="code"><pre>$ grails create-integration-test Example</pre></div><p class="paragraph"/>The above command will create a new integration test at the location <code>src/integration-test/groovy/<PACKAGE>/ExampleSpec.groovy</code>.<p class="paragraph"/>Grails uses the test environment for integration tests and loads the application prior to the first test run. All tests use the same application state.<p class="paragraph"/><h4>Transactions</h4><p class="paragraph"/>Integration tests run inside a database transaction by default, which is rolled back at the end of the each test. This means that data saved during a test is not persisted to the database (which is shared across all tests). The default generated integration test template includes the <a href="https://grails.apache.org/docs/3.0.x/api/grails/transaction/Rollback.html" class="api">Rollback</a> annotation:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.integration.Integration |
| <span class="java-keyword">import</span> grails.transaction.* |
| <span class="java-keyword">import</span> spock.lang.*<p class="paragraph"/>@Integration |
| @Rollback |
| class <code>artifact.name</code>Spec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> ...<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| expect:<span class="java-quote">"fix me"</span> |
| <span class="java-keyword">true</span> == <span class="java-keyword">false</span> |
| } |
| }</pre></div><p class="paragraph"/>The <code>Rollback</code> annotation ensures that each test methods runs in a transaction that is rolled back. Generally this is desirable because you do not want your tests depending on order or application state.<p class="paragraph"/><h4>Using Spring's Rollback annotation</h4> |
| In Grails 3.0 tests rely on <code>grails.transaction.Rollback</code> annotation to bind the session in integration tests. But with this approach the <code>setup()</code> and <code>setupSpec()</code> method in the test is run prior to the transaction starting hence |
| you would see <code>No Hibernate Session found</code> error while running integration test if <code>setup()</code> sets up data and persists them as shown in the below sample:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.integration.Integration |
| <span class="java-keyword">import</span> grails.transaction.* |
| <span class="java-keyword">import</span> spock.lang.*<p class="paragraph"/>@Integration |
| @Rollback |
| class <code>artifact.name</code>Spec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void setup() { |
| // Below line would <span class="java-keyword">throw</span> a Hibernate session not found error |
| <span class="java-keyword">new</span> Book(name: 'Grails in Action').save(flush: <span class="java-keyword">true</span>) |
| }<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| expect: |
| Book.count() == 1 |
| } |
| }</pre></div><p class="paragraph"/>To make sure the setup logic runs within the transaction you have to move it to be called from the method itself. Similar to usage of <code>setupData()</code> method shown below:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.integration.Integration |
| <span class="java-keyword">import</span> grails.transaction.* |
| <span class="java-keyword">import</span> spock.lang.*<p class="paragraph"/>@Integration |
| @Rollback |
| class <code>artifact.name</code>Spec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void setupData() { |
| <span class="java-keyword">new</span> Book(name: 'Grails in Action').save(flush: <span class="java-keyword">true</span>) |
| }<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| given: |
| setupData()<p class="paragraph"/> expect: |
| Book.count() == 1 |
| } |
| }</pre></div><p class="paragraph"/>Another approach could be to use Spring's <a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/annotation/Rollback.html" target="blank">@Rollback</a> instead.<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.integration.Integration |
| <span class="java-keyword">import</span> org.springframework.test.annotation.Rollback |
| <span class="java-keyword">import</span> spock.lang.*<p class="paragraph"/>@Integration |
| @Rollback |
| class <code>artifact.name</code>Spec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> void setup() { |
| <span class="java-keyword">new</span> Book(name: 'Grails in Action').save(flush: <span class="java-keyword">true</span>) |
| }<p class="paragraph"/> void <span class="java-quote">"test something"</span>() { |
| expect: |
| Book.count() == 1 |
| } |
| }</pre></div><p class="paragraph"/><blockquote class="note"> |
| It isn't possible to make <code>grails.transaction.Rollback</code> behave the same way as Spring's Rollback annotation because <code>grails.transaction.Rollback</code> transforms the byte code of the class, eliminating the need for a proxy (which Spring's version requires). |
| This has the downside that you cannot implement it differently for different cases (as Spring does for testing). |
| </blockquote><p class="paragraph"/><h4>DirtiesContext</h4><p class="paragraph"/>If you do have a series of tests that will share state you can remove the <code>Rollback</code> and the last test in the suite should feature the <a href="http://docs.spring.io/spring/docs/4.0.x/javadoc-api/org/springframework/test/annotation/DirtiesContext.html" class="api">DirtiesContext</a> annotation which will shutdown the environment and restart it fresh (note that this will have an impact on test run times).<p class="paragraph"/><h4>Autowiring</h4><p class="paragraph"/>To obtain a reference to a bean you can use the <a href="http://docs.spring.io/spring/docs/4.0.x/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html" class="api">Autowired</a> annotation. For example:<p class="paragraph"/><div class="code"><pre>… |
| <span class="java-keyword">import</span> org.springframework.beans.factory.annotation.*<p class="paragraph"/>@Integration |
| @Rollback |
| class <code>artifact.name</code>Spec <span class="java-keyword">extends</span> Specification {<p class="paragraph"/> @Autowired |
| ExampleService exampleService |
| ...<p class="paragraph"/> void <span class="java-quote">"Test example service"</span>() { |
| expect: |
| exampleService.countExamples() == 0 |
| } |
| }</pre></div><p class="paragraph"/> |
| <h4>Testing Controllers</h4><p class="paragraph"/>To integration test controllers it is recommended you use <a href="../ref/Command Line/create-functional-test.html" class="commandLine">create-functional-test</a> command to create a Geb functional test. See the following section on functional testing for more information. |
| |
| |
| <a name="12.3 Functional Testing"><!-- Legacy link --></a> |
| <h2 id="functionalTesting">14.3 Functional Testing</h2> |
| Functional tests involve making HTTP requests against the running application and verifying the resultant behaviour. The functional testing phase differs from the integration phase in that the Grails application is now listening and responding to actual HTTP requests. This is useful for end-to-end testing scenarios, such as making REST calls against a JSON API.<p class="paragraph"/>Grails by default ships with support for writing functional tests using the <a href="http://www.gebish.org" target="blank">Geb framework</a>. To create a functional test you can use the <code>create-functional-test</code> command which will create a new functional test:<p class="paragraph"/><div class="code"><pre>$ grails create-functional-test MyFunctional</pre></div><p class="paragraph"/>The above command will create a new Spock spec called <code>MyFunctionalSpec.groovy</code> in the <code>src/integration-test/groovy</code> directory. The test is annotated with the <a href="https://grails.apache.org/docs/3.0.x/api/grails/test/mixin/integration/Integration.html" class="api">Integration</a> annotation to indicate it is a integration test and extends the <code>GebSpec</code> super class:<p class="paragraph"/><div class="code"><pre>@Integration |
| class HomeSpec <span class="java-keyword">extends</span> GebSpec {<p class="paragraph"/> def setup() { |
| }<p class="paragraph"/> def cleanup() { |
| }<p class="paragraph"/> void <span class="java-quote">"Test the home page renders correctly"</span>() { |
| when:<span class="java-quote">"The home page is visited"</span> |
| go '/'<p class="paragraph"/> then:<span class="java-quote">"The title is correct"</span> |
| $('title').text() == <span class="java-quote">"Welcome to Grails"</span> |
| } |
| }</pre></div><p class="paragraph"/>When the test is run the application container will be loaded up in the background and you can send requests to the running application using the Geb API.<p class="paragraph"/>Note that the application is only loaded once for the entire test run, so functional tests share the state of the application across the whole suite.<p class="paragraph"/>In addition the application is loaded in the JVM as the test, this means that the test has full access to the application state and can interact directly with data services such as GORM to setup and cleanup test data.<p class="paragraph"/>The <code>Integration</code> annotation supports an optional <code>applicationClass</code> attribute which may be used to specify the application class to use for the functional test. The class must extend <a href="https://grails.apache.org/docs/3.0.x/api/grails/boot/config/GrailsAutoConfiguration.html" class="api">GrailsAutoConfiguration</a>.<p class="paragraph"/><div class="code"><pre>@Integration(applicationClass=com.demo.Application) |
| class HomeSpec <span class="java-keyword">extends</span> GebSpec {<p class="paragraph"/> // ...<p class="paragraph"/>}</pre></div><p class="paragraph"/>If the <code>applicationClass</code> is not specified then the test runtime environment will attempt to locate the application class dynamically which can be problematic in multiproject builds where multiple application classes may be present. |
| |
| |
| |
| <div style="clear:both;margin-top:15px;"></div> |
| |
| <div class="toc-item prev-left"><a href="../guide/staticTypeCheckingAndCompilation.html"><< <strong>13</strong><span>Static Type Checking And Compilation</span></a></div> |
| |
| <div class="toc-item next-right"><a href="../guide/i18n.html"><strong>15</strong><span>Internationalization</span> >></a></div> |
| |
| <div style="clear:both"></div> |
| </div> |
| </td> |
| <td id="col2"> |
| <div class="local clearfix"> |
| <div class="local-title"> |
| <a href="../guide/index.html" target="mainFrame">Quick Reference</a> |
| <span class="toggle">(<a href="#" onclick="localToggle(); return false;">hide</a>)</span> |
| </div> |
| <div class="menu"> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Command Line</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/bug-report.html">bug-report</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/clean.html">clean</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/compile.html">compile</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/console.html">console</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-app.html">create-app</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-command.html">create-command</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-controller.html">create-controller</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-domain-class.html">create-domain-class</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-functional-test.html">create-functional-test</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-hibernate-cfg-xml.html">create-hibernate-cfg-xml</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-integration-test.html">create-integration-test</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-interceptor.html">create-interceptor</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-plugin.html">create-plugin</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-script.html">create-script</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-service.html">create-service</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-taglib.html">create-taglib</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-unit-test.html">create-unit-test</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/dependency-report.html">dependency-report</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/docs.html">docs</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/generate-all.html">generate-all</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/generate-controller.html">generate-controller</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/generate-views.html">generate-views</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/help.html">help</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/install-templates.html">install-templates</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/list-plugins.html">list-plugins</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/package-plugin.html">package-plugin</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/package.html">package</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/plugin-info.html">plugin-info</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/run-app.html">run-app</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/schema-export.html">schema-export</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/shell.html">shell</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/stats.html">stats</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/stop-app.html">stop-app</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/test-app.html">test-app</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/war.html">war</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Constraints</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Constraints/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Constraints/attributes.html">attributes</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/bindable.html">bindable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/blank.html">blank</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/creditCard.html">creditCard</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/email.html">email</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/inList.html">inList</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/matches.html">matches</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/max.html">max</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/maxSize.html">maxSize</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/min.html">min</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/minSize.html">minSize</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/notEqual.html">notEqual</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/nullable.html">nullable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/range.html">range</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/scale.html">scale</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/size.html">size</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/unique.html">unique</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/url.html">url</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/validator.html">validator</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Constraints/widget.html">widget</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Controllers</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Controllers/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Controllers/actionName.html">actionName</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/afterInterceptor.html">afterInterceptor</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/allowedMethods.html">allowedMethods</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/beforeInterceptor.html">beforeInterceptor</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/bindData.html">bindData</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/chain.html">chain</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/controllerName.html">controllerName</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/defaultAction.html">defaultAction</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/errors.html">errors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/flash.html">flash</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/forward.html">forward</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/grailsApplication.html">grailsApplication</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/hasErrors.html">hasErrors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/namespace.html">namespace</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/params.html">params</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/redirect.html">redirect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/render.html">render</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/request.html">request</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/respond.html">respond</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/response.html">response</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/responseFormats.html">responseFormats</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/scope.html">scope</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/servletContext.html">servletContext</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/session.html">session</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/withForm.html">withForm</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Controllers/withFormat.html">withFormat</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Database Mapping</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/autoImport.html">autoImport</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/autoTimestamp.html">autoTimestamp</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/batchSize.html">batchSize</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/cache.html">cache</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/cascade.html">cascade</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/column.html">column</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/comment.html">comment</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/discriminator.html">discriminator</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/dynamicInsert.html">dynamicInsert</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/dynamicUpdate.html">dynamicUpdate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/fetch.html">fetch</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/id.html">id</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/ignoreNotFound.html">ignoreNotFound</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/indexColumn.html">indexColumn</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/insertable.html">insertable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/joinTable.html">joinTable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/lazy.html">lazy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/order.html">order</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/sort.html">sort</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/table.html">table</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/type.html">type</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/updateable.html">updateable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Database%20Mapping/version.html">version</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Domain Classes</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/addTo.html">addTo</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/attach.html">attach</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/belongsTo.html">belongsTo</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/clearErrors.html">clearErrors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/constraints.html">constraints</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/count.html">count</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/countBy.html">countBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/createCriteria.html">createCriteria</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/delete.html">delete</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/discard.html">discard</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/embedded.html">embedded</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/errors.html">errors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/executeQuery.html">executeQuery</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/executeUpdate.html">executeUpdate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/exists.html">exists</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/fetchMode.html">fetchMode</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/find.html">find</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findAll.html">findAll</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findAllBy.html">findAllBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findAllWhere.html">findAllWhere</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findBy.html">findBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findOrCreateBy.html">findOrCreateBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findOrCreateWhere.html">findOrCreateWhere</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findOrSaveBy.html">findOrSaveBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findOrSaveWhere.html">findOrSaveWhere</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/findWhere.html">findWhere</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/first.html">first</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/get.html">get</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/getAll.html">getAll</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/getDirtyPropertyNames.html">getDirtyPropertyNames</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/getPersistentValue.html">getPersistentValue</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/hasErrors.html">hasErrors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/hasMany.html">hasMany</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/hasOne.html">hasOne</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/ident.html">ident</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/instanceOf.html">instanceOf</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/isAttached.html">isAttached</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/isDirty.html">isDirty</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/last.html">last</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/list.html">list</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/listOrderBy.html">listOrderBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/load.html">load</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/lock.html">lock</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/mapWith.html">mapWith</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/mappedBy.html">mappedBy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/mapping.html">mapping</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/merge.html">merge</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/namedQueries.html">namedQueries</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/properties.html">properties</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/read.html">read</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/refresh.html">refresh</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/removeFrom.html">removeFrom</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/save.html">save</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/transients.html">transients</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/validate.html">validate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/where.html">where</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/whereAny.html">whereAny</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/withCriteria.html">withCriteria</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/withNewSession.html">withNewSession</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/withSession.html">withSession</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Domain%20Classes/withTransaction.html">withTransaction</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Plug-ins</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/URL%20mappings.html">URL mappings</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/codecs.html">codecs</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/controllers.html">controllers</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/core.html">core</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/dataSource.html">dataSource</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/domainClasses.html">domainClasses</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/hibernate.html">hibernate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/i18n.html">i18n</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/logging.html">logging</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/scaffolding.html">scaffolding</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/services.html">services</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Plug-ins/servlets.html">servlets</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Services</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Services/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Services/scope.html">scope</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Services/transactional.html">transactional</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Servlet API</h1><div class="menu-sub"> |
| |
| |
| <div class="menu-item"><a href="../ref/Servlet%20API/request.html">request</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Servlet%20API/response.html">response</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Servlet%20API/servletContext.html">servletContext</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Servlet%20API/session.html">session</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Tag Libraries</h1><div class="menu-sub"> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/Usage.html">Usage</a></div> |
| |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/actionName.html">actionName</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/controllerName.html">controllerName</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/flash.html">flash</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/pageScope.html">pageScope</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/params.html">params</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/request.html">request</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/response.html">response</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/servletContext.html">servletContext</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tag%20Libraries/session.html">session</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div class="menu-block"><h1 class="menu-title" onclick="toggleRef(this.parentNode.childNodes[1])">Tags</h1><div class="menu-sub"> |
| |
| |
| <div class="menu-item"><a href="../ref/Tags/actionSubmit.html">actionSubmit</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/actionSubmitImage.html">actionSubmitImage</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/applyLayout.html">applyLayout</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/checkBox.html">checkBox</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/collect.html">collect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/cookie.html">cookie</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/country.html">country</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/countrySelect.html">countrySelect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/createLink.html">createLink</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/createLinkTo.html">createLinkTo</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/currencySelect.html">currencySelect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/datePicker.html">datePicker</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/each.html">each</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/eachError.html">eachError</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/else.html">else</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/elseif.html">elseif</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/external.html">external</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/field.html">field</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/fieldError.html">fieldError</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/fieldValue.html">fieldValue</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/findAll.html">findAll</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/form.html">form</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/formatBoolean.html">formatBoolean</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/formatDate.html">formatDate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/formatNumber.html">formatNumber</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/grep.html">grep</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/hasErrors.html">hasErrors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/header.html">header</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/hiddenField.html">hiddenField</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/if.html">if</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/img.html">img</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/include.html">include</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/isAvailable.html">isAvailable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/isNotAvailable.html">isNotAvailable</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/javascript.html">javascript</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/join.html">join</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/layoutBody.html">layoutBody</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/layoutHead.html">layoutHead</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/layoutTitle.html">layoutTitle</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/link.html">link</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/localeSelect.html">localeSelect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/message.html">message</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/meta.html">meta</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/pageProperty.html">pageProperty</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/paginate.html">paginate</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/passwordField.html">passwordField</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/radio.html">radio</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/radioGroup.html">radioGroup</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/render.html">render</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/renderErrors.html">renderErrors</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/resource.html">resource</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/select.html">select</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/set.html">set</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/sortableColumn.html">sortableColumn</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/submitButton.html">submitButton</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/textArea.html">textArea</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/textField.html">textField</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/timeZoneSelect.html">timeZoneSelect</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/unless.html">unless</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/uploadForm.html">uploadForm</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/while.html">while</a> |
| </div> |
| |
| </div> |
| </div> |
| |
| </div> |
| </div> |
| </td> |
| </tr> |
| </table> |
| |
| <div id="footer"> |
| Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. |
| |
| </div> |
| |
| <script type="text/javascript" src="../js/docs.js"></script> |
| |
| </body> |
| </html> |