grails create-controller com.acme.app.simple
15 Testing
Version: 7.0.0-SNAPSHOT
Table of Contents
15 Testing
Automated testing is a critical aspect of Grails development. Grails provides a rich set of testing capabilities, ranging from low-level unit testing to high-level functional tests. This comprehensive guide explores these diverse testing features in detail.
15.1 Generating Tests
When you use the create-
and generate-
commands, Grails automatically generates unit
or integration
tests. For example, running the create-controller command as follows:
Grails generates a controller at grails-app/controllers/com/acme/app/SimpleController.groovy
, and a corresponding unit test at src/test/groovy/com/acme/app/SimpleControllerSpec.groovy
. It’s important to note that Grails only creates the test structure; you need to implement the test logic.
The default class name suffix is Spec as of Grails 3.x.
|
15.2 Running Tests
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 ways to run tests for Grails.
15.2.1 With Grails CLI
The grails test-app
command is one way to run Grails tests. This section documents how to use grails test-app
.
Running All Tests
Tests are run with the test-app command:
grails test-app
The command will produce output such as:
-------------------------------------------------------
Running Unit Tests...
Running test FooSpec...FAILURE
Unit Tests Completed in 464ms ...
-------------------------------------------------------
Tests failed: 0 errors, 1 failures
whilst showing the reason for each test failure.
You can force a clean before running tests by passing -clean to the test-app command.
|
Grails writes HTML test reports to the build/reports/tests
directory and JUnit XML test reports to the build/test-results
directory. The HTML reports are generally the best ones to look at.
Using Grails' interactive mode 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:
open test-report
You can also run your unit tests from within most IDEs.
Targeting Tests
You can selectively target the test(s) to be run in different ways. To run all tests for a controller named SimpleController
you would run:
grails test-app SimpleController
This will run any tests for the class named SimpleController
. Wildcards can be used…
grails test-app *Controller
This will test all classes ending in Controller
. Package names can optionally be specified…
grails test-app some.org.*Controller
or to run all tests in a package…
grails test-app some.org.*
or to run all tests in a package including subpackages…
grails test-app some.org.**.*
You can also target particular test methods…
grails test-app SimpleController.testLogin
This will run the testLogin
test in the SimpleController
tests. You can specify as many patterns in combination as you like…
grails test-app some.org.* SimpleController.testLogin BookController
In Grails 2.x, adding -rerun as an argument would only run those tests which failed in the previous test-app run. This argument is no longer supported.
|
In Grails 3.x, you might need to specify the package name before the class name, as well as append "Spec" to the end. For example, if you want to run the test for the ProductController, you should use grails test-app *.ProductControllerSpec . Note that the star can be used if you don’t want to type the whole package hierarchy.
|
Debugging
In order to debug your tests via a remote debugger, you can add --debug-jvm
after grails
in any commands, like so:
grails --debug-jvm test-app
This will open the default Java remote debugging port, 5005, for you to attach a remote debugger from your editor / IDE of choice.
This differs from Grails 2.3 and previous, where the grails-debug command existed.
|
Targeting Test Phases
In addition to targeting certain tests, you can also target test phases. By default Grails has two testing phases unit
and integration.
Grails 2.x uses phase:type syntax. In Grails 3.0 it was removed, because it made no sense in Gradle context.
|
To execute unit
tests you can run:
grails test-app -unit
To run integration
tests you would run…
grails test-app -integration
Targeting Tests When Using Phases
Test and phase targeting can be applied at the same time:
grails test-app some.org.**.* -unit
This would run all tests in the unit
phase that are in the package some.org
or a subpackage.
15.2.2 With Gradle
Running Tests
To execute tests, you can use the Gradle check task:
./gradlew check
This command will execute all the Unit tests in src/main/groovy/com/example/ directory.
Targeting Tests
To selectively target tests for execution, you have several options:
-
To run all tests for a controller named SimpleController, use this command:
./gradlew check --tests SimpleController
-
To test all classes ending in Controller, you can employ wildcards:
./gradlew check --tests *Controller
-
To specify package names:
./gradlew check --tests some.org.*Controller
-
To run all tests in a package:
./gradlew check --tests some.org.*
-
To run all tests in a package, including subpackages:
./gradlew check --tests some.org.**.*
-
To target specific test methods:
./gradlew check --tests SimpleController.testLogin
You can combine multiple patterns as needed:
./gradlew check --tests some.org.* SimpleController.testLogin BookController
You might need to specify the package name before the class name and append "Spec" to it. For instance, to run the test for the ProductController, use ./gradlew test *.ProductControllerSpec. You can also use the star wildcard if you want to avoid typing the entire package hierarchy. |
Debugging
To debug your tests using a remote debugger, you can add --debug-jvm
after ./gradlew
in any commands, like so:
./gradlew check --debug-jvm
This will open the default Java remote debugging port, 5005, allowing you to attach a remote debugger from your code editor or integrated development environment.
Targeting Test Phases / Running Unit & Integration Separately
To execute "unit" tests, use this command:
./gradlew test
For "integration" tests, you would run:
./gradlew integrationTest
Targeting Tests When Using Phases
You can combine test and phase targeting:
./gradlew test some.org.**.*
This command will run all tests in the unit phase within the some.org package or its subpackages. For more detailed information, it’s recommended to consult the Gradle documentation on Testing in Java & JVM projects.
15.3 Unit Testing
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 or interactions with other classes. Unit tests are typically run without the presence of physical resources that involve I/O such as databases, socket connections or files. This is to ensure they run as quick as possible since quick feedback is important.
The Grails Unit Testing Support provides support for writing concise expressive units tests for Grails artifacts with simple, easy to use traits and supporting classes. The use of Traits makes functionality woven into the unit test class at compile time, which allows developers to explicitly view and explore the code. In addition, the Grails Unit Testing Support makes testing more IDE-friendly so developers can take advantage of auto-completion and click through navigation.
15.3.1 Installation
To install the unit testing support library add the following dependency to the
dependencies
block of your build.gradle
in a Grails application or plugin:
testCompile "org.grails:grails-gorm-testing-support:7.0.0-SNAPSHOT"
testCompile "org.grails:grails-web-testing-support:7.0.0-SNAPSHOT"
The dependencies are only required to implement unit tests for Grails Artifacts. Also, if you are not unit testing domain activity, you may not need the GORM testing support library. |
15.3.2 Upgrading From The Mixin Framework
In prior Grails versions mixins were used to implement unit tests. The new unit tests libraries were designed to be compatible with this old mixin approach. There are some slight differences that you may encounter, however any required changes should be minimal.
FreshRuntime Removed
The @FreshRuntime
annotation was removed due to the design of the new framework. The annotation allowed any metaclass changes to be sandboxed either at the method or class level. Spock provides a similar annotation to do the same thing, @ConfineMetaClassChanges.
In addition, the annotation caused the application context to be refreshed. Because the application context is refreshed between test classes automatically, the annotation is no longer necessary.
Integration Tests
The @Integration
annotation was copied to the unit testing library so a dependency on the old framework is no longer necessary. The package has changed from grails.test.mixin.integration.Integration
to grails.testing.mixin.integration.Integration
. Everything about integration tests should work the same as before.
Example Converted Test
Here is an example test that may look like something in your project.
@TestFor(AuthorController)
@Mock(Author)
class AuthorControllerTests {
@Test
void testIndex() {
controller.index()
assert response.text == 'Hello'
}
}
To convert this test to the new framework, we must update it to use Spock as well as change the annotation usages to trait usages.
import spock.lang.Specification
import grails.testing.gorm.DomainUnitTest
import grails.testing.web.controllers.ControllerUnitTest
class AuthorControllerTests extends Specification implements ControllerUnitTest<AuthorController>, DomainUnitTest<Author> {
void testIndex() {
when:
controller.index()
then:
response.text == 'Hello'
}
}
Obviously there are many use cases you may want to convert and it would be impossible to cover them all in this documentation. As a part of the testing of this new framework, all of the usages of the old framework were replaced in Grails core. You can view those changes by looking at this commit to see the vast majority of examples you will need.
15.3.3 Unit Testing Controllers
Use the grails.testing.web.controllers.ControllerUnitTest
trait to unit
test controllers.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/DemoControllerSpec.groovy[tags=basic_declaration,indent=0]
To test the simplest "Hello World"-style example you can do the following:
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/controllers/demo/DemoController.groovy[tags=basic_declaration;render_hello,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/DemoControllerSpec.groovy[tags=basic_declaration;test_render,indent=0]
1 | The controller property will be an instance of DemoController |
2 | The status property will contain the value of the response status |
3 | The response property will be a reference to the HTTP response object |
See the ControllerUnitTest
docs for information on all of the available properties.
In an effort to make testing controllers that render JSON views easy, a change was made that required the controller variable used in the test code to be a proxy that delegates to the real controller instance. If for some reason that causes an issue with your test, it is possible to disable the creation of a proxy by overriding a method. |
class DemoControllerSpec implements ControllerUnitTest<DemoController> {
boolean disableControllerProxy() {
true
}
}
By doing so, JSON views will not be rendered by default. To enable JSON views to render automatically as before, it is necessary to inform the webRequest
object which action you are invoking.
class DemoControllerSpec implements ControllerUnitTest<DemoController> {
void "test index"() {
when:
webRequest.actionName = 'index'
controller.index()
then:
...
}
boolean disableControllerProxy() {
true
}
}
15.3.4 Unit Testing Domain Classes
Use the grails.testing.gorm.DomainUnitTest
trait to unit test single domain class.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/domain/demo/Person.groovy[indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/PersonSpec.groovy[indent=0]
Alternatively, the grails.testing.gorm.DataTest
trait may be used. When using DataTest
, an explicit call to the
mockDomain or
mockDomains
method may be used to specify which domain class(es) should be mocked for this
test. This is useful when mocking more than one Domain class at a time to test persistence.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/DataTestTraitSpec.groovy[indent=0]
Another way to express which domain classes should be mocked for this test is
to provide a Class[] getDomainClassesToMock()
method in the test.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/GetDomainClassesToMockMethodSpec.groovy[indent=0]
When mocking domain classes in a test for another artifact type (like a
ControllerUnitTest
test, for example), the test must implement the DataTest
trait in order to mock the related domain classes.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/PersonControllerSpec.groovy[indent=0]
15.3.5 Unit Testing Services
Use the grails.testing.services.ServiceUnitTest
trait to unit
test services.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/services/demo/HelperService.groovy[tags=basic_declaration,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/HelperServiceSpec.groovy[tags=basic_declaration,indent=0]
Adding the ServiceUnitTest
trait to a test causes a new service
property to
be automatically created for the Service class under test.
15.3.6 Unit Testing Tag Libraries
The Basics
Tag libraries and GSP pages can be tested with the
grails.testing.web.taglib.TagLibUnitTest
trait.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/taglib/demo/SampleTagLib.groovy[tags=basic_declaration;hello_world,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/SampleTagLibSpec.groovy[tags=basic_declaration;test_simple_tag_as_method,indent=0]
Adding the TagLibUnitTest
trait to a test causes a new tagLib
field to be
automatically created for the TagLib class under test. The tagLib
property can
be used to test calling tags as function calls. The return value of a function
call is either a org.grails.buffer,StreamCharBuffer
instance or the object returned from the tag closure when
returnObjectForTags
feature is used.
To test a tag which accepts parameters, specify the parameter values as named arguments to the method call.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/taglib/demo/SampleTagLib.groovy[tags=basic_declaration;say_hello,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/SampleTagLibSpec.groovy[tags=basic_declaration;test_tag_as_method_with_parameters,indent=0]
Alternatively, tags may be tested with the applyTemplate
method which accepts
a String
parameter that will be evaluated as if it were source code in a GSP.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/SampleTagLibSpec.groovy[tags=basic_declaration;test_simple_tag_with_applyTemplate,indent=0]
The applyTemplate
method accepts an optional second argument which is a Map
containing model variables which may be accessed in the GSP snippet that is
past as the first argument to applyTemplate
as shown below.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/taglib/demo/SampleTagLib.groovy[tags=basic_declaration;render_some_number,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/SampleTagLibSpec.groovy[tags=basic_declaration;test_with_model,indent=0]
The String being passed as the first argument to applyTemplate includes
a Groovy String expression ("${x + y}" ) that needs to be evaluated when the GSP snippet is
evaluated, not when the code in the test is evaluated. Because of that it is
important that the containing String be surrounded by single quotes, not double
quotes. '<demo:renderSomeNumber value="${x + y}"/>' works.
"<demo:renderSomeNumber value='${x + y}'/>" would not.
|
Mocking Tag Libraries
In order to test a tag library which invokes tags from another tag library,
the second tag library needs to be explicitly mocked by invoking the
mockTagLib
method.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/taglib/demo/FirstTagLib.groovy[tags=basic_declaration,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/taglib/demo/SecondTagLib.groovy[tags=basic_declaration,indent=0]
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/FirstTagLibSpec.groovy[tags=basic_declaration,indent=0]
15.3.7 Unit Testing Interceptors
Use the InterceptorUnitTest
trait to unit test interceptors.
The interceptor unit test trait provides methods to make testing interceptors easy.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/controllers/demo/TestInterceptor.groovy[tags=declaration,indent=0]
withRequest
You can use the withRequest
method in combination with interceptor.doesMatch()
to verify whether or not your interceptor matches the request.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/TestInterceptorSpec.groovy[tags=with_request,indent=0]
withInterceptors
You can use the withInterceptors
method to execute code within the context of interceptor execution. This is typically done to call controller actions that rely on behavior from interceptors.
Given this controller action:
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/controllers/demo/TestController.groovy[tags=render_attribute,indent=0]
Here is how the action might be tested with withInterceptors
:
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/TestInterceptorSpec.groovy[tags=with_interceptors,indent=0]
Adding the InterceptorUnitTest
trait to a test causes a new interceptor
property to
be automatically created for the Interceptor class under test.
15.3.8 Unit Testing Url Mappings
Use the UrlMappingsUnitTest
trait to unit test url mappings. Testing url mappings also requires controllers to be mocked to match the mappings to.
Controllers can not be mocked in setupSpec due to the nature of the request being created and reset for each test. The request is not available until the setup method, therefore controllers can not be mocked until then.
|
All of the methods that check the url mappings come in 2 forms, assert
and verify
. The assert
versions will throw AssertionFailed exceptions, similar to the assert
keyword in Groovy. The verify
methods will simply return true or false depending on whether the url mapping was found and is valid for the expectations.
The examples assume the following mappings are being used.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/grails-app/controllers/demo/UrlMappings.groovy[indent=0]
Getting Started
To get started, implement the UrlMappingsUnitTest
in your test class and mock controllers you would like to test against.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=setup,indent=0]
It is also possible call the mockController
method in the given blocks of your feature methods if different controllers need to be tested in different test methods.
Forward Url Mapping Test
Tests whether a URL mapping is forwarded for the given controller class
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=forward,indent=0]
Reverse Url Mapping Test
Test whether the given URL is produced when reverse mapping a link to a given controller and action
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=reverse,indent=0]
Url mappings for HTTP status codes can not be reversed because it doesn’t make sense to "link" to a status code. |
Combined
Tests whether a URL mapping is valid for the given URL. This combines the forward and reverse methods.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=combined,indent=0]
When calling verifyUrlMapping , then reverse mapping will only be checked if a controller is supplied and the first parameter is not an HTTP status code.
|
HTTP Methods
When testing HTTP methods on reverse URL mapping it is necessary to specify the HTTP method in the test.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=httpMethodsReverse,indent=0]
When testing HTTP methods on forward URL mapping it is necessary to specify the HTTP method in the request.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=httpMethodsForward,indent=0]
When testing HTTP methods on both forward and reverse URL mapping combined it is necessary to specify the HTTP method in both the request and in the test.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=httpMethodsCombined,indent=0]
Other Helpful Methods
Controller Check
Use the verifyController
method to check whether or not the given controller name exists.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=controller,indent=0]
Action Check
Use the verifyAction
method to verify if an action exists for a controller.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=action,indent=0]
View Check
User the verifyView
method to check if a GSP exists for a controller.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/test/groovy/demo/UrlMappingsSpec.groovy[tags=view,indent=0]
15.3.9 Annotations
@RunOnce
The grails.testing.spock.RunOnce
annotation may be applied to any Spock test
fixture method that you wish to be executed only once. This is useful when
applied in conjunction with a fixture annnotation like @Before
as shown below.
package grails.testing.spock
import org.junit.jupiter.api.BeforeEach
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Stepwise
@Stepwise
class RunOnceSpec extends Specification {
@Shared
int setupSpecCounter = 0
@Shared
int setupCounter = 0
@Shared
int onceBeforeCounter = 0
@Shared
int anotherOnceBeforeCounter = 0
void setupSpec() {
setupSpecCounter++
}
void setup() {
setupCounter++
}
@BeforeEach
@RunOnce
void someOnceBeforeMethod() {
onceBeforeCounter++
}
@BeforeEach
@RunOnce
void someOtherOnceBeforeMethod() {
anotherOnceBeforeCounter++
}
void 'first test'() {
expect:
setupSpecCounter == 1
setupCounter == 1
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
void 'second test'() {
expect:
setupSpecCounter == 1
setupCounter == 2
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
void 'third test'() {
expect:
setupSpecCounter == 1
setupCounter == 3
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
}
Applying both the @RunOnce
and @Before
annotations to a method will yield
behavior similar to the behavior associated with Spock’s setupSpec
method but
an important difference is that setupSpec
is run before the test instance is
subjected to dependency injection while @Before
methods are run after the
test instance is subjected to dependency injection. This means that the
setupSpec
method will not have access to injected variables but methods marked
with @Before
will have access to injected variables. If a test has some one
time setup logic that needs to be executed after dependency injection happens,
the RunOnce
annotation can help accomplish that.
@OnceBefore
The grails.testing.spock.OnceBefore
annotation is a shorthand way of
accomplishing the same behavior that would be accomplished by applying both the
@RunOnce
and @Before
annotations to a fixture method.
package grails.testing.spock
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Stepwise
@Stepwise
class OnceBeforeSpec extends Specification {
@Shared
int setupSpecCounter = 0
@Shared
int setupCounter = 0
@Shared
int onceBeforeCounter = 0
@Shared
int anotherOnceBeforeCounter = 0
void setupSpec() {
setupSpecCounter++
}
void setup() {
setupCounter++
}
@OnceBefore
void someOnceBeforeMethod() {
onceBeforeCounter++
}
@OnceBefore
void someOtherOnceBeforeMethod() {
anotherOnceBeforeCounter++
}
void 'first test'() {
expect:
setupSpecCounter == 1
setupCounter == 1
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
void 'second test'() {
expect:
setupSpecCounter == 1
setupCounter == 2
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
void 'third test'() {
expect:
setupSpecCounter == 1
setupCounter == 3
onceBeforeCounter == 1
anotherOnceBeforeCounter == 1
}
}
This is useful in the context of an integration test which wants to reference dependency injected values during setup as shown below.
Unresolved directive in <stdin> - include::/home/runner/work/grails-doc/grails-doc/build/grails-functional-tests/checkout/grails-functional-tests-src/demo33/src/integration-test/groovy/demo/DependencyInjectionSpec.groovy[indent=0]
15.3.10 Useful Properties
The testing framework provides and initializes a number of properties that are directly accessible unit tests. The javadocs for the various traits describe those properties. Some particular properties of interest:
Properties Available In All Unit Tests
Properties Available In All Web Unit Tests (Controller, Interceptor, Taglib, UrlMappings)
Controller Unit Test Properties
Interceptor Unit Test Properties
Service Unit Test Properties
Tag Library Unit Test Properties
Domain Class Unit Test Properties
15.4 Integration Testing
Integration tests differ from unit tests in that you have full access to the Grails environment within the test. You can create an integration test using the create-integration-test command:
$ grails create-integration-test Example
The above command will create a new integration test at the location src/integration-test/groovy/<PACKAGE>/ExampleSpec.groovy
.
Grails uses the test environment for integration tests and loads the application prior to the first test run. All tests use the same application state.
Transactions
Integration test methods run inside their own database transaction by default, which is rolled back at the end of each test method. This means that data saved during a test is not persisted to the database (which is shared across all tests). The default generated integration test template includes the Rollback annotation:
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class ExampleSpec extends Specification {
...
void "test something"() {
expect:"fix me"
true == false
}
}
The Rollback
annotation ensures that each test method runs in a transaction that is rolled back. Generally this is desirable because you do not want your tests depending on order or application state.
In Grails 3.0 tests rely on grails.gorm.transactions.Rollback
annotation to bind the session in integration tests. Though each test method transaction is rolled back, the setup()
method uses a separate transaction that is not rolled back.
Data will persist to the database and will need to be cleaned up manually if setup()
sets up data and persists them as shown in the below sample:
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setup() {
// Below line would persist and not roll back
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
expect:
Book.count() == 1
}
}
To preload the database and automatically roll back setup logic, any persistence operations need to be called from the test method itself so that they can run within the test method’s rolled back transaction. Similar to usage of the setupData()
method shown below which creates a record in database and after running other test will be rolled back:
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setupData() {
// Below line would roll back
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
given:
setupData()
expect:
Book.count() == 1
}
}
Using Spring’s Rollback annotation
Another transactional approach could be to use Spring’s @Rollback instead.
import grails.testing.mixin.integration.Integration
import org.springframework.test.annotation.Rollback
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setup() {
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
expect:
Book.count() == 1
}
}
It isn’t possible to make grails.gorm.transactions.Rollback behave the same way as Spring’s Rollback annotation because grails.gorm.transactions.Rollback transforms the byte code of the class, eliminating the need for a proxy (which Spring’s version requires).
This has the downside that you cannot implement it differently for different cases (as Spring does for testing).
|
DirtiesContext
If you do have a series of tests that will share state you can remove the Rollback
and the last test in the suite should feature the DirtiesContext annotation which will shutdown the environment and restart it fresh (note that this will have an impact on test run times).
Autowiring
To obtain a reference to a bean you can use the Autowired annotation. For example:
...
import org.springframework.beans.factory.annotation.*
@Integration
@Rollback
class ExampleServiceSpec extends Specification {
@Autowired
ExampleService exampleService
...
void "Test example service"() {
expect:
exampleService.countExamples() == 0
}
}
Testing Controllers
To integration test controllers it is recommended you use create-functional-test command to create a Geb functional test. See the following section on functional testing for more information.
15.5 Functional Testing
Functional tests involve making HTTP requests against the running application and verifying the resultant behaviour. This is useful for end-to-end testing scenarios, such as making REST calls against a JSON API.
Grails by default ships with support for writing functional tests using the Geb framework. To create a functional test you can use the create-functional-test
command which will create a new functional test:
$ grails create-functional-test MyFunctional
The above command will create a new Spock spec called MyFunctionalSpec.groovy
in the src/integration-test/groovy
directory. The test is annotated with the Integration annotation to indicate it is an integration test and extends the GebSpec
super class:
@Integration
class HomeSpec extends GebSpec {
def setup() {
}
def cleanup() {
}
void "Test the home page renders correctly"() {
when:"The home page is visited"
go '/'
then:"The title is correct"
$('title').text() == "Welcome to Grails"
}
}
When the test is run the application container will be loaded up in the background and you can send requests to the running application using the Geb API.
Note that the application is only loaded once for the entire test run, so functional tests share the state of the application across the whole suite.
In addition the application is loaded in the JVM as the test, this means that the test has full access to the application state and can interact directly with data services such as GORM to setup and cleanup test data.
The Integration
annotation supports an optional applicationClass
attribute which may be used to specify the application class to use for the functional test. The class must extend GrailsAutoConfiguration.
@Integration(applicationClass=com.demo.Application)
class HomeSpec extends GebSpec {
// ...
}
If the applicationClass
is not specified then the test runtime environment will attempt to locate the application class dynamically which can be problematic in multiproject builds where multiple application classes may be present.
When running the server port by default will be randomly assigned. The Integration
annotation adds a property of serverPort
to the test class that you can use if you want to know what port the application is running on this isn’t needed if you are extending the GebSpec
as shown above but can be useful information.
If you want to run the tests on a fixed port (defined by the server.port
configuration property), you need to manually annotate your test with @SpringBootTest
:
import grails.testing.mixin.integration.Integration
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
@Integration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class MySpec extends Specification {
// ...
}