| <!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>9 Testing 2.0.1</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/conf.html"><strong>3</strong><span>Configuration</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/commandLine.html"><strong>4</strong><span>The Command Line</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/GORM.html"><strong>5</strong><span>Object Relational Mapping (GORM)</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/theWebLayer.html"><strong>6</strong><span>The Web Layer</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/validation.html"><strong>7</strong><span>Validation</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/services.html"><strong>8</strong><span>The Service Layer</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/testing.html"><strong>9</strong><span>Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/i18n.html"><strong>10</strong><span>Internationalization</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/security.html"><strong>11</strong><span>Security</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/plugins.html"><strong>12</strong><span>Plugins</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/webServices.html"><strong>13</strong><span>Web Services</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/spring.html"><strong>14</strong><span>Grails and Spring</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/hibernate.html"><strong>15</strong><span>Grails and Hibernate</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/scaffolding.html"><strong>16</strong><span>Scaffolding</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/deployment.html"><strong>17</strong><span>Deployment</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0"><a href="../guide/contributing.html"><strong>18</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> |
| |
| |
| <span id="sponsor"><a href="http://springsource.com" target="_blank"><img alt="SpringSource Logo" title="SpringSource - Weapons for the War on Java Complexity" src="../img/springsource-logo.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/services.html"><< <strong>8</strong><span>The Service Layer</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>10</strong><span>Internationalization</span> >></a></div> |
| |
| |
| |
| <div class="project"> |
| <h1>9 Testing - Reference Documentation</h1> |
| |
| <p><strong>Authors:</strong> Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith</p> |
| |
| <p><strong>Version:</strong> 2.0.1</p> |
| |
| |
| </div> |
| |
| |
| <div id="table-of-content"> |
| <h2>Table of Contents</h2> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#unitTesting"><strong>9.1</strong><span>Unit Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingControllers"><strong>9.1.1</strong><span>Unit Testing Controllers</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingTagLibraries"><strong>9.1.2</strong><span>Unit Testing Tag Libraries</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingDomains"><strong>9.1.3</strong><span>Unit Testing Domains</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingFilters"><strong>9.1.4</strong><span>Unit Testing Filters</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#unitTestingURLMappings"><strong>9.1.5</strong><span>Unit Testing URL Mappings</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:10px"><a href="#mockingCollaborators"><strong>9.1.6</strong><span>Mocking Collaborators</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#integrationTesting"><strong>9.2</strong><span>Integration Testing</span></a> |
| </div> |
| |
| <div class="toc-item" style="margin-left:0px"><a href="#functionalTesting"><strong>9.3</strong><span>Functional Testing</span></a> |
| </div> |
| |
| </div> |
| |
| |
| |
| <a name="9. Testing"><!-- Legacy link --></a> |
| <h1 id="testing">9 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 Types and/or Phases</h4><p class="paragraph"/>In addition to targeting certain tests, you can also target test <em class="italic">types</em> and/or <em class="italic">phases</em> by using the <code>phase:type</code> syntax.<p class="paragraph"/><blockquote class="note"> |
| Grails organises tests by phase and by type. A test phase relates to the state of the Grails application during the tests, and the type relates to the testing mechanism.<p class="paragraph"/>Grails comes with support for 4 test phases (<code>unit</code>, <code>integration</code>, <code>functional</code> and <code>other</code>) and JUnit test types for the <code>unit</code> and <code>integration</code> phases. These test types have the same name as the phase.<p class="paragraph"/>Testing plugins may provide new test phases or new test types for existing phases. Refer to the plugin documentation. |
| </blockquote><p class="paragraph"/>To execute the JUnit <code>integration</code> tests you can run:<p class="paragraph"/><div class="code"><pre>grails test-app integration:integration</pre></div><p class="paragraph"/>Both <code>phase</code> and <code>type</code> are optional. Their absence acts as a wildcard. The following command will run all test types in the <code>unit</code> phase:<p class="paragraph"/><div class="code"><pre>grails test-app unit:</pre></div><p class="paragraph"/>The Grails <a href="http://grails.org/plugin/spock" target="blank">Spock Plugin</a> is one plugin that adds new test types to Grails. It adds a <code>spock</code> test type to the <code>unit</code>, <code>integration</code> and <code>functional</code> phases. To run all spock tests in all phases you would run the following:<p class="paragraph"/><div class="code"><pre>grails test-app :spock</pre></div><p class="paragraph"/>To run the all of the spock tests in the <code>functional</code> phase you would run...<p class="paragraph"/><div class="code"><pre>grails test-app functional:spock</pre></div><p class="paragraph"/>More than one pattern can be specified...<p class="paragraph"/><div class="code"><pre>grails test-app unit:spock integration:spock</pre></div><p class="paragraph"/><h4>Targeting Tests in Types and/or Phases</h4><p class="paragraph"/>Test and type/phase targetting can be applied at the same time:<p class="paragraph"/><div class="code"><pre>grails test-app integration: unit: some.org.**.*</pre></div><p class="paragraph"/>This would run all tests in the <code>integration</code> and <code>unit</code> phases that are in the package <code>some.org</code> or a subpackage. |
| |
| |
| <a name="9.1 Unit Testing"><!-- Legacy link --></a> |
| <h2 id="unitTesting">9.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="https://grails.apache.org/docs/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.TestMixin</code></li> |
| <li><code>grails.test.mixin.Mock</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.webflow/WebFlowUnitTestMixin</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 a test a controller and associated domains you would define the following:<p class="paragraph"/><div class="code"><pre>@TestFor(BookController) |
| <code>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@ annotation creates mock version of any collaborators. There is an in-memory implementation of GORM that will simulate most interactions with the GORM API. For those interactions that are not automatically mocked you can use the built in support for defining mocks and stubs programmatically. For example:<p class="paragraph"/> |
| <div class="code"><pre>void testSearch() { |
| def control = mockFor(SearchService) |
| control.demand.searchWeb { <span class="java-object">String</span> q -> ['mock results'] } |
| control.demand.<span class="java-keyword">static</span>.logResults { List results -> } |
| controller.searchService = control.createMock() |
| controller.search()<p class="paragraph"/> assert controller.response.text.contains <span class="java-quote">"Found 1 results"</span> |
| }</pre></div> |
| |
| |
| <a name="9.1.1 Unit Testing Controllers"><!-- Legacy link --></a> |
| <h2 id="unitTestingControllers">9.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<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerTests { |
| void testSomething() {<p class="paragraph"/> } |
| }</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>void testHello() { |
| controller.hello()<p class="paragraph"/> assert 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>// Test class |
| class SimpleController { |
| def index() { |
| redirect action: 'hello' |
| } |
| … |
| }</pre></div><p class="paragraph"/><div class="code"><pre>void testIndex() { |
| controller.index()<p class="paragraph"/> assert 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>void testList() { |
| params.sort = <span class="java-quote">"name"</span> |
| params.max = 20 |
| params.offset = 0<p class="paragraph"/> controller.list() |
| … |
| }</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>void testSave() { |
| request.method = <span class="java-quote">"POST"</span> |
| controller.save() |
| … |
| }</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>void testGetPage() { |
| request.method = <span class="java-quote">"POST"</span> |
| request.makeAjaxRequest() |
| controller.getPage() |
| … |
| }</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>// Test class |
| 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>void testIndex() { |
| controller.home()<p class="paragraph"/> assert view == <span class="java-quote">"/simple/homePage"</span> |
| assert model.title == <span class="java-quote">"Hello World"</span> |
| }</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>void testDisplay() { |
| controller.display() |
| assert response.text == 'contents of 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>void testDisplay() { |
| views['/simple/_snippet.gsp'] = 'mock contents' |
| controller.display() |
| assert response.text == 'mock 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.*<p class="paragraph"/>@TestFor(SimpleController) |
| class SimpleControllerTests {<p class="paragraph"/> void testShowBookDetails() { |
| def model = controller.showBookDetails()<p class="paragraph"/> assert 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>void testRenderXml() { |
| controller.renderXml() |
| assert <span class="java-quote">"<book title='Great'/>"</span> == response.text |
| assert <span class="java-quote">"Great"</span> == response.xml.@title.text() |
| }</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">"text/json"</span>) { |
| book = <span class="java-quote">"Great"</span> |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test |
| void testRenderJson() {<p class="paragraph"/> controller.renderJson()<p class="paragraph"/> assert '{<span class="java-quote">"book"</span>:<span class="java-quote">"Great"</span>}' == response.text |
| assert <span class="java-quote">"Great"</span> == response.json.book |
| }</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() { |
| def b = <span class="java-keyword">new</span> Book(params['book'])<p class="paragraph"/> render b.title |
| }</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>void testConsumeBookXml() { |
| request.xml = '<book><title>The Shining</title></book>' |
| controller.consumeBook()<p class="paragraph"/> assert response.text == 'The Shining' |
| }</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>void testConsumeBookXml() { |
| request.xml = <span class="java-keyword">new</span> Book(title:<span class="java-quote">"The Shining"</span>) |
| controller.consumeBook()<p class="paragraph"/> assert response.text == 'The Shining' |
| }</pre></div><p class="paragraph"/>The same can be done for JSON requests:<p class="paragraph"/><div class="code"><pre>void testConsumeBookJson() { |
| request.json = <span class="java-keyword">new</span> Book(title:<span class="java-quote">"The Shining"</span>) |
| controller.consumeBook()<p class="paragraph"/> assert response.text == 'The Shining' |
| }</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 request.XML.@title |
| } |
| json { |
| render request.JSON.title |
| } |
| } |
| }</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>void testConsumeXml() { |
| request.xml = '<book title=<span class="java-quote">"The Stand"</span> />'<p class="paragraph"/> controller.consume()<p class="paragraph"/> assert response.text == 'The Stand' |
| }</pre></div><p class="paragraph"/>And, of course, the same can be done for JSON:<p class="paragraph"/><div class="code"><pre>void testConsumeJson() { |
| request.json = '{title:<span class="java-quote">"The Stand"</span>}' |
| controller.consume()<p class="paragraph"/> assert response.text == 'The Stand' |
| }</pre></div><p class="paragraph"/><h4>Testing Spring Beans</h4><p class="paragraph"/>When using <code>TestFor</code> only a subset of the Spring beans available to a running Grails application are available. If you wish to make additional beans available you can do so with the <code>defineBeans</code> method of <code>GrailsUnitTestMixin</code>:<p class="paragraph"/><div class="code"><pre>class SimpleController { |
| SimpleService simpleService |
| def hello() { |
| render simpleService.sayHello() |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>void testBeanWiring() { |
| defineBeans { |
| simpleService(SimpleService) |
| }<p class="paragraph"/> controller.hello()<p class="paragraph"/> assert response.text == <span class="java-quote">"Hello World"</span> |
| }</pre></div><p class="paragraph"/>The controller is auto-wired by Spring just like in a running Grails application. Autowiring even occurs if you instantiate subsequent instances of the controller:<p class="paragraph"/><div class="code"><pre>void testAutowiringViaNew() { |
| defineBeans { |
| simpleService(SimpleService) |
| }<p class="paragraph"/> def controller1 = <span class="java-keyword">new</span> SimpleController() |
| def controller2 = <span class="java-keyword">new</span> SimpleController()<p class="paragraph"/> assert controller1.simpleService != <span class="java-keyword">null</span> |
| assert controller2.simpleService != <span class="java-keyword">null</span> |
| }</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 response's <code>format</code> attribute:<p class="paragraph"/><div class="code"><pre>// controller action |
| def sayHello() { |
| def data = [Hello:<span class="java-quote">"World"</span>] |
| withFormat { |
| xml { render data as XML } |
| html data |
| } |
| }</pre></div><p class="paragraph"/><div class="code"><pre>// test |
| void testSayHello() { |
| response.format = 'xml' |
| controller.sayHello()<p class="paragraph"/> <span class="java-object">String</span> expected = '<?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"/> assert expected == response.text |
| }</pre></div><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>void testDuplicateFormSubmission() { |
| controller.handleForm() |
| assert <span class="java-quote">"Bad"</span> == response.text |
| }</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> org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerToken |
| ...<p class="paragraph"/>void testValidFormSubmission() { |
| def token = SynchronizerToken.store(session) |
| params[SynchronizerToken.KEY] = token.currentToken.toString()<p class="paragraph"/> controller.handleForm() |
| assert <span class="java-quote">"Good"</span> == response.text |
| }</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>controller.handleForm() // first execution |
| … |
| response.reset() |
| … |
| controller.handleForm() // second execution</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>void testFileUpload() { |
| <span class="java-keyword">final</span> file = <span class="java-keyword">new</span> GrailsMockMultipartFile(<span class="java-quote">"myFile"</span>, <span class="java-quote">"foo"</span>.bytes) |
| request.addFile(file) |
| controller.uploadFile()<p class="paragraph"/> assert file.targetFileLocation.path == <span class="java-quote">"/local/disk/myFile"</span> |
| }</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>def handleCommand(SimpleCommand simple) { |
| <span class="java-keyword">if</span> (simple.hasErrors()) { |
| render <span class="java-quote">"Bad"</span> |
| } |
| <span class="java-keyword">else</span> { |
| render <span class="java-quote">"Good"</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>void testInvalidCommand() { |
| def cmd = mockCommandObject(SimpleCommand) |
| cmd.name = '' // doesn't allow blank names<p class="paragraph"/> cmd.validate() |
| controller.handleCommand(cmd)<p class="paragraph"/> assert response.text == 'Bad' |
| }</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>void testRenderBasicTemplateWithTags() { |
| messageSource.addMessage(<span class="java-quote">"foo.bar"</span>, request.locale, <span class="java-quote">"Hello World"</span>)<p class="paragraph"/> controller.showMessage()<p class="paragraph"/> assert response.text == <span class="java-quote">"Hello World"</span> |
| }</pre></div> |
| |
| |
| <a name="9.1.2 Unit Testing Tag Libraries"><!-- Legacy link --></a> |
| <h2 id="unitTestingTagLibraries">9.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>@TestFor(SimpleTagLib) |
| class SimpleTagLibTests {<p class="paragraph"/>}</pre></div><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>@TestFor(SimpleController) |
| @Mock(SimpleTagLib) |
| class GroovyPageUnitTestMixinTests {<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> |
| } |
| }</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>@TestFor(SimpleTagLib) |
| class SimpleTagLibTests { |
| void testHelloTag() { |
| assert applyTemplate('<s:hello />') == 'Hello World' |
| assert applyTemplate('<s:hello name=<span class="java-quote">"Fred"</span> />') == 'Hello 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>@grails.test.mixin.TestMixin(GroovyPageUnitTestMixin) |
| class MultipleTagLibraryTests {<p class="paragraph"/> @Test |
| void testMuliple() { |
| mockTagLib(FirstTagLib) |
| mockTagLib(SecondTagLib)<p class="paragraph"/> … |
| } |
| }</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>@grails.test.mixin.TestMixin(GroovyPageUnitTestMixin) |
| class MultipleTagLibraryTests {<p class="paragraph"/> @Test |
| void testMuliple() { |
| mockTagLib(FirstTagLib) |
| mockTagLib(SecondTagLib) |
| 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>def result = render(template: <span class="java-quote">"/simple/hello"</span>) |
| assert result == <span class="java-quote">"Hello World"</span></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. |
| |
| |
| <a name="9.1.3 Unit Testing Domains"><!-- Legacy link --></a> |
| <h2 id="unitTestingDomains">9.1.3 Unit Testing Domains</h2> |
| <h4>Overview</h4><p class="paragraph"/><blockquote class="note"> |
| The mocking support described here is best used when testing non-domain artifacts that use domain classes, to let you focus on testing the artifact without needing a database. But when testing persistence it's best to use integration tests which configure Hibernate and use a database. |
| </blockquote><p class="paragraph"/>Domain class interaction can be tested without involving a database connection using <code>DomainClassUnitTestMixin</code>. This implementation mimics the behavior of GORM against an in-memory <code>ConcurrentHashMap</code> implementation. Note that this has limitations compared to a real GORM implementation. The following features of GORM for Hibernate can only be tested within an integration test: |
| <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"/>However 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"/>If something isn't supported then <code>GrailsUnitTestMixin</code>'s <code>mockFor</code> method can come in handy to mock the missing pieces. Alternatively you can write an integration test which bootstraps the complete Grails environment at a cost of test execution time.<p class="paragraph"/><h4>The 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.*<p class="paragraph"/>@TestFor(SimpleController) |
| @Mock(Simple) |
| class SimpleControllerTests {<p class="paragraph"/>}</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])}<span class="java-quote">" |
| redirect(action: "</span>show<span class="java-quote">", id: book.id) |
| } |
| <span class="java-keyword">else</span> { |
| render(view: "</span>create", 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.*<p class="paragraph"/>@TestFor(BookController) |
| @Mock(Book) |
| class BookControllerTests {<p class="paragraph"/> void testSaveInvalidBook() { |
| controller.save()<p class="paragraph"/> assert model.bookInstance != <span class="java-keyword">null</span> |
| assert view == '/book/create' |
| }<p class="paragraph"/> void testSaveValidBook() { |
| 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"/> assert response.redirectedUrl == '/book/show/1' |
| assert flash.message != <span class="java-keyword">null</span> |
| assert 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>@TestFor(BookController) |
| @Mock([Book, Author]) |
| class BookControllerTests { |
| … |
| }</pre></div><p class="paragraph"/>Alternatively you can also use the <code>DomainClassUnitTestMixin</code> directly with the <code>TestMixin</code> annotation:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.mixin.domain.DomainClassUnitTestMixin<p class="paragraph"/>@TestFor(BookController) |
| @TestMixin(DomainClassUnitTestMixin) |
| class BookControllerTests { |
| … |
| }</pre></div><p class="paragraph"/>And then call the <code>mockDomain</code> method to mock domains during your test:<p class="paragraph"/><div class="code"><pre>void testSave() { |
| mockDomain(Author) |
| mockDomain(Book) |
| }</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>void testSave() { |
| 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"/>Your constraints contain logic and that logic is highly susceptible to bugs - the kind of bugs that can be tricky to track down (particularly as by default <code>save()</code> doesn't throw an exception when it fails). If your answer is that it's too hard or fiddly, that is no longer an excuse. Enter the <code>mockForConstraintsTests()</code> method.<p class="paragraph"/>This method is like a much reduced version of the <code>mockDomain()</code> method that simply adds a <code>validate()</code> method to a given domain class. All you have to do is mock the class, create an instance with populated data, and then call <code>validate()</code>. You can then access the <code>errors</code> property to determine if validation failed. So if all we are doing is mocking the <code>validate()</code> method, why the optional list of test instances? That is so that we can test the <code>unique</code> constraint as you will soon see.<p class="paragraph"/>So, suppose we have a simple domain class:<p class="paragraph"/><div class="code"><pre>class Book {<p class="paragraph"/> <span class="java-object">String</span> title |
| <span class="java-object">String</span> author<p class="paragraph"/> <span class="java-keyword">static</span> constraints = { |
| title blank: <span class="java-keyword">false</span>, unique: <span class="java-keyword">true</span> |
| author blank: <span class="java-keyword">false</span>, minSize: 5 |
| } |
| }</pre></div><p class="paragraph"/>Don't worry about whether the constraints are sensible (they're not!), they are for demonstration only. To test these constraints we can do the following:<p class="paragraph"/><div class="code"><pre>@TestFor(Book) |
| class BookTests { |
| void testConstraints() {<p class="paragraph"/> def existingBook = <span class="java-keyword">new</span> Book( |
| title: <span class="java-quote">"Misery"</span>, |
| author: <span class="java-quote">"Stephen King"</span>)<p class="paragraph"/> mockForConstraintsTests(Book, [existingBook])<p class="paragraph"/> // validation should fail <span class="java-keyword">if</span> both properties are <span class="java-keyword">null</span> |
| def book = <span class="java-keyword">new</span> Book()<p class="paragraph"/> assert !book.validate() |
| assert <span class="java-quote">"nullable"</span> == book.errors[<span class="java-quote">"title"</span>] |
| assert <span class="java-quote">"nullable"</span> == book.errors[<span class="java-quote">"author"</span>]<p class="paragraph"/> // So let's demonstrate the unique and minSize constraints<p class="paragraph"/> book = <span class="java-keyword">new</span> Book(title: <span class="java-quote">"Misery"</span>, author: <span class="java-quote">"JK"</span>) |
| assert !book.validate() |
| assert <span class="java-quote">"unique"</span> == book.errors[<span class="java-quote">"title"</span>] |
| assert <span class="java-quote">"minSize"</span> == book.errors[<span class="java-quote">"author"</span>]<p class="paragraph"/> // Validation should pass! |
| book = <span class="java-keyword">new</span> Book(title: <span class="java-quote">"The Shining"</span>, author: <span class="java-quote">"Stephen King"</span>) |
| assert book.validate() |
| } |
| }</pre></div><p class="paragraph"/>You can probably look at that code and work out what's happening without any further explanation. The one thing we will explain is the way the <code>errors</code> property is used. First, is a real Spring <code>Errors</code> instance, so you can access all the properties and methods you would normally expect. Second, this particular <code>Errors</code> object also has map/property access as shown. Simply specify the name of the field you are interested in and the map/property access will return the name of the constraint that was violated. Note that it is the constraint name, not the message code (as you might expect).<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! It is currently one of the hardest bugs to track down normally, and yet a unit test for your constraints will highlight the problem straight away. |
| |
| |
| <a name="9.1.4 Unit Testing Filters"><!-- Legacy link --></a> |
| <h2 id="unitTestingFilters">9.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>@TestFor(SimpleController) |
| @Mock(CancellingFilters) |
| class SimpleControllerTests {<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>void testInvocationOfListActionIsFiltered() { |
| withFilters(action:<span class="java-quote">"list"</span>) { |
| controller.list() |
| } |
| assert 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 a 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="9.1.5 Unit Testing URL Mappings"><!-- Legacy link --></a> |
| <h2 id="unitTestingURLMappings">9.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>@TestFor(UrlMappings) |
| class UrlMappingsTests {<p class="paragraph"/>}</pre></div><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">"/action1"</span>(controller: <span class="java-quote">"simple"</span>, action: <span class="java-quote">"action1"</span>) |
| <span class="java-quote">"/action2"</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>void testUrlMappings() {<p class="paragraph"/> assertForwardUrlMapping(<span class="java-quote">"/action1"</span>, controller: 'simple', |
| action: <span class="java-quote">"action1"</span>)<p class="paragraph"/> assertForwardUrlMapping(<span class="java-quote">"/action2"</span>, controller: 'simple', |
| action: <span class="java-quote">"action2"</span>)<p class="paragraph"/> shouldFail { |
| assertForwardUrlMapping(<span class="java-quote">"/action2"</span>, controller: 'simple', |
| action: <span class="java-quote">"action1"</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>.<p class="paragraph"/><h4>Simulating Controller Mapping</h4><p class="paragraph"/>In addition to the assertions to check the validity of URL mappings you can also simulate mapping to a controller by using your <code>UrlMappings</code> as a mock collaborator and the <code>mapURI</code> method. For example:<p class="paragraph"/><div class="code"><pre>@TestFor(SimpleController) |
| @Mock(UrlMappings) |
| class SimpleControllerTests {<p class="paragraph"/> void testControllerMapping() {<p class="paragraph"/> SimpleController controller = mapURI('/simple/list') |
| assert controller != <span class="java-keyword">null</span><p class="paragraph"/> def model = controller.list() |
| assert model != <span class="java-keyword">null</span> |
| } |
| }</pre></div> |
| |
| |
| <a name="9.1.6 Mocking Collaborators"><!-- Legacy link --></a> |
| <h2 id="mockingCollaborators">9.1.6 Mocking Collaborators</h2> |
| Beyond the specific targeted mocking APIs there is also an all-purpose <code>mockFor()</code> method that is available when using the <code>TestFor</code> annotation. The signature of <code>mockFor</code> is:<p class="paragraph"/><div class="code"><pre>mockFor(class, loose = <span class="java-keyword">false</span>)</pre></div><p class="paragraph"/>This is general-purpose mocking that lets you set up either strict or loose demands on a class.<p class="paragraph"/>This method is surprisingly intuitive to use. By default it will create a strict mock control object (one for which the order in which methods are called is important) that you can use to specify demands:<p class="paragraph"/><div class="code"><pre>def strictControl = mockFor(MyService) |
| strictControl.demand.someMethod(0..2) { <span class="java-object">String</span> arg1, <span class="java-object">int</span> arg2 -> … } |
| strictControl.demand.<span class="java-keyword">static</span>.aStaticMethod {-> … }</pre></div><p class="paragraph"/>Notice that you can mock static as well as instance methods by using the "static" property. You then specify the name of the method to mock, with an optional range argument. This range determines how many times you expect the method to be called, and if the number of invocations falls outside of that range (either too few or too many) then an assertion error will be thrown. If no range is specified, a default of "1..1" is assumed, i.e. that the method must be called exactly once.<p class="paragraph"/>The last part of a demand is a closure representing the implementation of the mock method. The closure arguments must match the number and types of the mocked method, but otherwise you are free to add whatever you want in the body.<p class="paragraph"/>Call <code>mockControl.createMock()</code> to get an actual mock instance of the class that you are mocking. You can call this multiple times to create as many mock instances as you need. And once you have executed the test method, call <code>mockControl.verify()</code> to check that the expected methods were called.<p class="paragraph"/>Lastly, the call:<p class="paragraph"/><div class="code"><pre>def looseControl = mockFor(MyService, <span class="java-keyword">true</span>)</pre></div><p class="paragraph"/>will create a mock control object that has only loose expectations, i.e. the order that methods are invoked does not matter. |
| |
| |
| <a name="9.2 Integration Testing"><!-- Legacy link --></a> |
| <h2 id="integrationTesting">9.2 Integration Testing</h2> |
| Integration tests differ from unit tests in that you have full access to the Grails environment within the test. Grails uses an in-memory H2 database for integration tests and clears out all the data from the database between tests.<p class="paragraph"/>One thing to bear in mind is that logging is enabled for your application classes, but it is different from logging in tests. So if you have something like this:<p class="paragraph"/><div class="code"><pre>class MyServiceTests <span class="java-keyword">extends</span> GroovyTestCase { |
| void testSomething() { |
| log.info <span class="java-quote">"Starting tests"</span> |
| … |
| } |
| }</pre></div><p class="paragraph"/>the "starting tests" message is logged using a different system than the one used by the application. The <code>log</code> property in the example above is an instance of <code>java.util.logging.Logger</code> (inherited from the base class, not injected by Grails), which doesn't have the same methods as the <code>log</code> property injected into your application artifacts. For example, it doesn't have <code>debug()</code> or <code>trace()</code> methods, and the equivalent of <code>warn()</code> is in fact <code>warning()</code>.<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. Add a <code>transactional</code> property to your test class to check transactional behaviour:<p class="paragraph"/><div class="code"><pre>class MyServiceTests <span class="java-keyword">extends</span> GroovyTestCase { |
| <span class="java-keyword">static</span> transactional = <span class="java-keyword">false</span><p class="paragraph"/> void testMyTransactionalServiceMethod() { |
| … |
| } |
| }</pre></div><p class="paragraph"/>Be sure to remove any persisted data from a non-transactional test, for example in the <code>tearDown</code> method, so these tests don't interfere with standard transactional tests that expect a clean database.<p class="paragraph"/><h4>Testing Controllers</h4><p class="paragraph"/>To test controllers you first have to understand the Spring Mock Library.<p class="paragraph"/>Grails automatically configures each test with a <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/mock/web/MockHttpServletRequest.html" class="api">MockHttpServletRequest</a>, <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/mock/web/MockHttpServletResponse.html" class="api">MockHttpServletResponse</a>, and <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/mock/web/MockHttpSession.html" class="api">MockHttpSession</a> that you can use in your tests. For example consider the following controller:<p class="paragraph"/><div class="code"><pre>class FooController {<p class="paragraph"/> def text() { |
| render <span class="java-quote">"bar"</span> |
| }<p class="paragraph"/> def someRedirect() { |
| redirect(action:<span class="java-quote">"bar"</span>) |
| } |
| }</pre></div><p class="paragraph"/>The tests for this would be:<p class="paragraph"/><div class="code"><pre>class FooControllerTests <span class="java-keyword">extends</span> GroovyTestCase {<p class="paragraph"/> void testText() { |
| def fc = <span class="java-keyword">new</span> FooController() |
| fc.text() |
| assertEquals <span class="java-quote">"bar"</span>, fc.response.contentAsString |
| }<p class="paragraph"/> void testSomeRedirect() { |
| def fc = <span class="java-keyword">new</span> FooController() |
| fc.someRedirect() |
| assertEquals <span class="java-quote">"/foo/bar"</span>, fc.response.redirectedUrl |
| } |
| }</pre></div><p class="paragraph"/>In the above case <code>response</code> is an instance of <code>MockHttpServletResponse</code> which we can use to obtain the generated content with <code>contentAsString</code> (when writing to the response) or the redirected URL. These mocked versions of the Servlet API are completely mutable (unlike the real versions) and hence you can set properties on the request such as the <code>contextPath</code> and so on.<p class="paragraph"/>Grails <strong class="bold">does not</strong> invoke <a href="../guide/single.html#interceptors" class="guide">interceptors</a> or servlet filters when calling actions during integration testing. You should test interceptors and filters in isolation, using <a href="../guide/single.html#functionalTesting" class="guide">functional testing</a> if necessary.<p class="paragraph"/><h4>Testing Controllers with Services</h4><p class="paragraph"/>If your controller references a service (or other Spring beans), you have to explicitly initialise the service from your test.<p class="paragraph"/>Given a controller using a service:<p class="paragraph"/><div class="code"><pre>class FilmStarsController { |
| def popularityService<p class="paragraph"/> def update() { |
| // <span class="java-keyword">do</span> something with popularityService |
| } |
| }</pre></div><p class="paragraph"/>The test for this would be:<p class="paragraph"/><div class="code"><pre>class FilmStarsTests <span class="java-keyword">extends</span> GroovyTestCase { |
| def popularityService<p class="paragraph"/> void testInjectedServiceInController () { |
| def fsc = <span class="java-keyword">new</span> FilmStarsController() |
| fsc.popularityService = popularityService |
| fsc.update() |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing Controller Command Objects</h4><p class="paragraph"/>With command objects you just supply parameters to the request and it will automatically do the command object work for you when you call your action with no parameters:<p class="paragraph"/>Given a controller using a command object:<p class="paragraph"/><div class="code"><pre>class AuthenticationController { |
| def signup(SignupForm form) { |
| … |
| } |
| }</pre></div><p class="paragraph"/>You can then test it like this:<p class="paragraph"/><div class="code"><pre>def controller = <span class="java-keyword">new</span> AuthenticationController() |
| controller.params.login = <span class="java-quote">"marcpalmer"</span> |
| controller.params.password = <span class="java-quote">"secret"</span> |
| controller.params.passwordConfirm = <span class="java-quote">"secret"</span> |
| controller.signup()</pre></div><p class="paragraph"/>Grails auto-magically sees your call to <code>signup()</code> as a call to the action and populates the command object from the mocked request parameters. During controller testing, the <code>params</code> are mutable with a mocked request supplied by Grails.<p class="paragraph"/><h4>Testing Controllers and the render Method</h4><p class="paragraph"/>The <a href="../ref/Controllers/render.html" class="controllers">render</a> method lets you render a custom view at any point within the body of an action. For instance, consider the example below:<p class="paragraph"/><div class="code"><pre>def save() { |
| def book = Book(params) |
| <span class="java-keyword">if</span> (book.save()) { |
| // handle |
| } |
| <span class="java-keyword">else</span> { |
| render(view:<span class="java-quote">"create"</span>, model:[book:book]) |
| } |
| }</pre></div><p class="paragraph"/>In the above example the result of the model of the action is not available as the return value, but instead is stored within the <code>modelAndView</code> property of the controller. The <code>modelAndView</code> property is an instance of Spring MVC's <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/ModelAndView.html" target="blank">ModelAndView</a> class and you can use it to the test the result of an action:<p class="paragraph"/><div class="code"><pre>def bookController = <span class="java-keyword">new</span> BookController() |
| bookController.save() |
| def model = bookController.modelAndView.model.book</pre></div><p class="paragraph"/><h4>Simulating Request Data</h4><p class="paragraph"/>You can use the Spring <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/mock/web/MockHttpServletRequest.html" class="api">MockHttpServletRequest</a> to test an action that requires request data, for example a REST web service. For example consider this action which performs data binding from an incoming request:<p class="paragraph"/><div class="code"><pre>def create() { |
| [book: <span class="java-keyword">new</span> Book(params.book)] |
| }</pre></div><p class="paragraph"/>To simulate the 'book' parameter as an XML request you could do something like the following:<p class="paragraph"/><div class="code"><pre>void testCreateWithXML() {<p class="paragraph"/> def controller = <span class="java-keyword">new</span> BookController()<p class="paragraph"/> controller.request.contentType = 'text/xml' |
| controller.request.content = '''\ <?xml version=<span class="java-quote">"1.0"</span> encoding=<span class="java-quote">"ISO-8859-1"</span>?> |
| <book> |
| <title>The Stand</title> |
| … |
| </book> |
| '''.stripIndent().getBytes() // note we need the bytes<p class="paragraph"/> def model = controller.create() |
| assert model.book |
| assertEquals <span class="java-quote">"The Stand"</span>, model.book.title |
| }</pre></div><p class="paragraph"/>The same can be achieved with a JSON request:<p class="paragraph"/><div class="code"><pre>void testCreateWithJSON() {<p class="paragraph"/> def controller = <span class="java-keyword">new</span> BookController()<p class="paragraph"/> controller.request.contentType = <span class="java-quote">"text/json"</span> |
| controller.request.content = |
| '{<span class="java-quote">"id"</span>:1,<span class="java-quote">"class"</span>:<span class="java-quote">"Book"</span>,<span class="java-quote">"title"</span>:<span class="java-quote">"The Stand"</span>}'.getBytes()<p class="paragraph"/> def model = controller.create() |
| assert model.book |
| assertEquals <span class="java-quote">"The Stand"</span>, model.book.title |
| }</pre></div><p class="paragraph"/><blockquote class="note"> |
| With JSON don't forget the <code>class</code> property to specify the name the target type to bind to. In XML this is implicit within the name of the <code><book></code> node, but this property is required as part of the JSON packet. |
| </blockquote><p class="paragraph"/>For more information on the subject of REST web services see the section on <a href="../guide/single.html#REST" class="guide">REST</a>.<p class="paragraph"/><h4>Testing Web Flows</h4><p class="paragraph"/>Testing <a href="../guide/single.html#webflow" class="guide">Web Flows</a> requires a special test harness called <code>grails.test.WebFlowTestCase</code> which subclasses Spring Web Flow's <a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/webflow/test/execution/AbstractFlowExecutionTests.html" class="api">AbstractFlowExecutionTests</a> class.<p class="paragraph"/><blockquote class="note"> |
| Subclasses of <code>WebFlowTestCase</code> <strong class="bold">must</strong> be integration tests |
| </blockquote><p class="paragraph"/>For example given this simple flow:<p class="paragraph"/><div class="code"><pre>class ExampleController {<p class="paragraph"/> def exampleFlow() { |
| start { |
| on(<span class="java-quote">"go"</span>) { |
| flow.hello = <span class="java-quote">"world"</span> |
| }.to <span class="java-quote">"next"</span> |
| } |
| next { |
| on(<span class="java-quote">"back"</span>).to <span class="java-quote">"start"</span> |
| on(<span class="java-quote">"go"</span>).to <span class="java-quote">"subber"</span> |
| } |
| subber { |
| subflow(action: <span class="java-quote">"sub"</span>) |
| on(<span class="java-quote">"end"</span>).to(<span class="java-quote">"end"</span>) |
| } |
| end() |
| }<p class="paragraph"/> def subFlow() { |
| subSubflowState { |
| subflow(controller: <span class="java-quote">"other"</span>, action: <span class="java-quote">"otherSub"</span>) |
| on(<span class="java-quote">"next"</span>).to(<span class="java-quote">"next"</span>) |
| } |
| … |
| } |
| }</pre></div><p class="paragraph"/>You need to tell the test harness what to use for the "flow definition". This is done via overriding the abstract <code>getFlow</code> |
| method:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> grails.test.WebFlowTestCase<p class="paragraph"/>class ExampleFlowTests <span class="java-keyword">extends</span> WebFlowTestCase { |
| def getFlow() { <span class="java-keyword">new</span> ExampleController().exampleFlow } |
| … |
| }</pre></div><p class="paragraph"/>You can specify the flow id by overriding the <code>getFlowId</code> method, otherwise the default is <code>test</code>: |
| <div class="code"><pre><span class="java-keyword">import</span> grails.test.WebFlowTestCase<p class="paragraph"/>class ExampleFlowTests <span class="java-keyword">extends</span> WebFlowTestCase { |
| <span class="java-object">String</span> getFlowId() { <span class="java-quote">"example"</span> } |
| … |
| }</pre></div><p class="paragraph"/>If the flow under test calls any subflows, these (or mocks) must be registered before the calling the flow: |
| <div class="code"><pre><span class="java-keyword">protected</span> void setUp() { |
| <span class="java-keyword">super</span>.setUp()<p class="paragraph"/> registerFlow(<span class="java-quote">"other/otherSub"</span>) { // register a simplified mock |
| start { |
| on(<span class="java-quote">"next"</span>).to(<span class="java-quote">"end"</span>) |
| } |
| end() |
| }<p class="paragraph"/> // register the original subflow |
| registerFlow(<span class="java-quote">"example/sub"</span>, <span class="java-keyword">new</span> ExampleController().subFlow) |
| }</pre></div><p class="paragraph"/>Then you kick off the flow with the <code>startFlow</code> method:<p class="paragraph"/><div class="code"><pre>void testExampleFlow() { |
| def viewSelection = startFlow() |
| … |
| }</pre></div><p class="paragraph"/>Use the <code>signalEvent</code> method to trigger an event:<p class="paragraph"/><div class="code"><pre>void testExampleFlow() { |
| … |
| signalEvent(<span class="java-quote">"go"</span>) |
| assert <span class="java-quote">"next"</span> == flowExecution.activeSession.state.id |
| assert <span class="java-quote">"world"</span> == flowScope.hello |
| }</pre></div><p class="paragraph"/>Here we have signaled to the flow to execute the event "go" which causes a transition to the "next" state. In the example a transition action placed a <code>hello</code> variable into the flow scope.<p class="paragraph"/><h4>Testing Tag Libraries</h4><p class="paragraph"/>Testing tag libraries is simple because when a tag is invoked as a method it returns its result as a string (technically a <code>StreamCharBuffer</code> but this class implements all of the methods of <code>String</code>). So for example if you have a tag library like this:<p class="paragraph"/><div class="code"><pre>class FooTagLib {<p class="paragraph"/> def bar = { attrs, body -> |
| out << <span class="java-quote">"<p>Hello World!</p>"</span> |
| }<p class="paragraph"/> def bodyTag = { attrs, body -> |
| out << <span class="java-quote">"<${attrs.name}>"</span> |
| out << body() |
| out << <span class="java-quote">"</${attrs.name}>"</span> |
| } |
| }</pre></div><p class="paragraph"/>The tests would look like:<p class="paragraph"/><div class="code"><pre>class FooTagLibTests <span class="java-keyword">extends</span> GroovyTestCase {<p class="paragraph"/> void testBarTag() { |
| assertEquals <span class="java-quote">"<p>Hello World!</p>"</span>, |
| <span class="java-keyword">new</span> FooTagLib().bar(<span class="java-keyword">null</span>, <span class="java-keyword">null</span>).toString() |
| }<p class="paragraph"/> void testBodyTag() { |
| assertEquals <span class="java-quote">"<p>Hello World!</p>"</span>, |
| <span class="java-keyword">new</span> FooTagLib().bodyTag(name: <span class="java-quote">"p"</span>) { |
| <span class="java-quote">"Hello World!"</span> |
| }.toString() |
| } |
| }</pre></div><p class="paragraph"/>Notice that for the second example, <code>testBodyTag</code>, we pass a block that returns the body of the tag. This is convenient to representing the body as a String.<p class="paragraph"/><h4>Testing Tag Libraries with GroovyPagesTestCase</h4><p class="paragraph"/>In addition to doing simple testing of tag libraries like in the above examples, you can also use the <code>grails.test.GroovyPagesTestCase</code> class to test tag libraries with integration tests.<p class="paragraph"/>The <code>GroovyPagesTestCase</code> class is a subclass of the standard <code>GroovyTestCase</code> class and adds utility methods for testing the output of GSP rendering.<p class="paragraph"/><blockquote class="note"> |
| <code>GroovyPagesTestCase</code> can only be used in an integration test. |
| </blockquote><p class="paragraph"/>For example, consider this date formatting tag library:<p class="paragraph"/><div class="code"><pre><span class="java-keyword">import</span> java.text.SimpleDateFormat<p class="paragraph"/>class FormatTagLib { |
| def dateFormat = { attrs, body -> |
| out << <span class="java-keyword">new</span> SimpleDateFormat(attrs.format) << attrs.date |
| } |
| }</pre></div><p class="paragraph"/>This can be easily tested as follows:<p class="paragraph"/><div class="code"><pre>class FormatTagLibTests <span class="java-keyword">extends</span> GroovyPagesTestCase { |
| void testDateFormat() { |
| def template = |
| '<g:dateFormat format=<span class="java-quote">"dd-MM-yyyy"</span> date=<span class="java-quote">"${myDate}"</span> />'<p class="paragraph"/> def testDate = … // create the date |
| assertOutputEquals('01-01-2008', template, [myDate:testDate]) |
| } |
| }</pre></div><p class="paragraph"/>You can also obtain the result of a GSP using the <code>applyTemplate</code> method of the <code>GroovyPagesTestCase</code> class:<p class="paragraph"/><div class="code"><pre>class FormatTagLibTests <span class="java-keyword">extends</span> GroovyPagesTestCase { |
| void testDateFormat() { |
| def template = |
| '<g:dateFormat format=<span class="java-quote">"dd-MM-yyyy"</span> date=<span class="java-quote">"${myDate}"</span> />'<p class="paragraph"/> def testDate = … // create the date |
| def result = applyTemplate(template, [myDate:testDate])<p class="paragraph"/> assertEquals '01-01-2008', result |
| } |
| }</pre></div><p class="paragraph"/><h4>Testing Domain Classes</h4><p class="paragraph"/>Testing domain classes is typically a simple matter of using the <a href="../guide/single.html#GORM" class="guide">GORM API</a>, but there are a few things to be aware of. Firstly, when testing queries you often need to "flush" to ensure the correct state has been persisted to the database. For example take the following example:<p class="paragraph"/><div class="code"><pre>void testQuery() { |
| def books = [ |
| <span class="java-keyword">new</span> Book(title: <span class="java-quote">"The Stand"</span>), |
| <span class="java-keyword">new</span> Book(title: <span class="java-quote">"The Shining"</span>)] |
| books*.save()<p class="paragraph"/> assertEquals 2, Book.list().size() |
| }</pre></div><p class="paragraph"/>This test will fail because calling <a href="../ref/Domain Classes/save.html" class="domainClasses">save</a> does not actually persist the <code>Book</code> instances when called. Calling <code>save</code> only indicates to Hibernate that at some point in the future these instances should be persisted. To commit changes immediately you "flush" them:<p class="paragraph"/><div class="code"><pre>void testQuery() { |
| def books = [ |
| <span class="java-keyword">new</span> Book(title: <span class="java-quote">"The Stand"</span>), |
| <span class="java-keyword">new</span> Book(title: <span class="java-quote">"The Shining"</span>)] |
| books*.save(flush: <span class="java-keyword">true</span>)<p class="paragraph"/> assertEquals 2, Book.list().size() |
| }</pre></div><p class="paragraph"/>In this case since we're passing the argument <code>flush</code> with a value of <code>true</code> the updates will be persisted immediately and hence will be available to the query later on. |
| |
| |
| <a name="9.3 Functional Testing"><!-- Legacy link --></a> |
| <h2 id="functionalTesting">9.3 Functional Testing</h2> |
| Functional tests involve making HTTP requests against the running application and verifying the resultant behaviour. Grails does not ship with any support for writing functional tests directly, but there are several plugins available for this. |
| <ul class="star"> |
| <li><code>Canoo Webtest</code> - <a href="http://grails.org/plugin/webtest" target="blank">http://grails.org/plugin/webtest</a></li> |
| <li><code>G-Func</code> - <a href="http://grails.org/plugin/functional-test" target="blank">http://grails.org/plugin/functional-test</a></li> |
| <li><code>Geb</code> - <a href="http://grails.org/plugin/geb" target="blank">http://grails.org/plugin/geb</a></li> |
| <li><code>Selenium-RC</code> - <a href="http://grails.org/plugin/selenium-rc" target="blank">http://grails.org/plugin/selenium-rc</a></li> |
| <li><code>WebDriver</code> - <a href="http://grails.org/plugin/webdriver" target="blank">http://grails.org/plugin/webdriver</a></li> |
| </ul><p class="paragraph"/>Consult the documentation for each plugin for its capabilities.<p class="paragraph"/><h4>Common Options</h4><p class="paragraph"/>There are options that are common to all plugins that control how the Grails application is launched, if at all.<p class="paragraph"/><h5>inline</h5><p class="paragraph"/>The <code>-inline</code> option specifies that the grails application should be started inline (i.e. like <code>run-app</code>).<p class="paragraph"/><strong class="bold">This option is implicitly set unless the <code>baseUrl</code> or <code>war</code> options are set</strong><p class="paragraph"/><h5>war</h5><p class="paragraph"/>The <code>-war</code> option specifies that the grails application should be packaged as a war and started. This is useful as it tests your application in a production-like state, but it has a longer startup time than the <code>-inline</code> option. It also runs the war in a forked JVM, meaning that you cannot access any internal application objects.<p class="paragraph"/><div class="code"><pre>grails test-app functional: -war</pre></div><p class="paragraph"/>Note that the same build/config options for the <a href="../ref/Command Line/run-war.html" class="commandLine">run-war</a> command apply to functional testing against the WAR.<p class="paragraph"/><h5>https</h5><p class="paragraph"/>The <code>-https</code> option results in the application being able to receive https requests as well as http requests. It is compatible with both the <code>-inline</code> and <code>-war</code> options.<p class="paragraph"/><div class="code"><pre>grails test-app functional: -https</pre></div><p class="paragraph"/>Note that this does not change the test <em class="italic">base url</em> to be https, it will still be http unless the <code>-httpsBaseUrl</code> option is also given.<p class="paragraph"/><h5>httpsBaseUrl</h5><p class="paragraph"/>The <code>-httpsBaseUrl</code> causes the implicit base url to be used for tests to be a https url.<p class="paragraph"/><div class="code"><pre>grails test-app functional: -httpsBaseUrl</pre></div><p class="paragraph"/>This option is ignored if the <code>-baseUrl</code> option is specified.<p class="paragraph"/><h5>baseUrl</h5><p class="paragraph"/>The <code>baseUrl</code> option allows the base url for tests to be specified.<p class="paragraph"/><div class="code"><pre>grails test-app functional: -baseUrl=http://mycompany.com/grailsapp</pre></div><p class="paragraph"/>This option will prevent the local grails application being started unless <code>-inline</code> or <code>-war</code> are given as well. To use a custom base url but still test against the local Grails application you <strong class="bold">must</strong> specify one of either the <code>-inline</code> or <code>-war</code> options. |
| |
| |
| |
| <div style="clear:both;margin-top:15px;"></div> |
| |
| <div class="toc-item prev-left"><a href="../guide/services.html"><< <strong>8</strong><span>The Service Layer</span></a></div> |
| |
| <div class="toc-item next-right"><a href="../guide/i18n.html"><strong>10</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/add-proxy.html">add-proxy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/bootstrap.html">bootstrap</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/clear-proxy.html">clear-proxy</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-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-filters.html">create-filters</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-plugin.html">create-plugin</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/create-scaffold-controller.html">create-scaffold-controller</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-tag-lib.html">create-tag-lib</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/doc.html">doc</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/init.html">init</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/install-dependency.html">install-dependency</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/install-plugin.html">install-plugin</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/integrate-with.html">integrate-with</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/interactive.html">interactive</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/list-plugin-updates.html">list-plugin-updates</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/migrate-docs.html">migrate-docs</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/refresh-dependencies.html">refresh-dependencies</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/remove-proxy.html">remove-proxy</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/run-script.html">run-script</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/run-war.html">run-war</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/set-proxy.html">set-proxy</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/set-version.html">set-version</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/test-app.html">test-app</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/uninstall-plugin.html">uninstall-plugin</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Command%20Line/upgrade.html">upgrade</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/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/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/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/response.html">response</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/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/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/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/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/filters.html">filters</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 class="menu-item"><a href="../ref/Plug-ins/web%20flow.html">web flow</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/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/formRemote.html">formRemote</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/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/remoteField.html">remoteField</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/remoteFunction.html">remoteFunction</a> |
| </div> |
| |
| <div class="menu-item"><a href="../ref/Tags/remoteLink.html">remoteLink</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/setProvider.html">setProvider</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/submitToRemote.html">submitToRemote</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. |
| Sponsored by <a href="http://springsource.com">SpringSource</a> |
| </div> |
| |
| <script type="text/javascript" src="../js/docs.js"></script> |
| |
| </body> |
| </html> |