blob: 9dc16e079b03b39e9c9649883086aa7b97bf4bc2 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Testing Guide
== Introduction
The Groovy programming language comes with great support for writing tests. In addition to the language
features and test integration with state-of-the-art testing libraries and frameworks, the Groovy ecosystem has born
a rich set of testing libraries and frameworks.
This chapter will start with language specific testing features and continue with a closer look at JUnit
integration, Spock for specifications, and Geb for functional tests. Finally, we'll do an overview of other testing
libraries known to be working with Groovy.
== Language Features
Besides integrated support for JUnit, the Groovy programming language comes with features that have proven
to be very valuable for test-driven development. This section gives insight on them.
=== Power Assertions
Writing tests means formulating assumptions by using assertions. In Java this can be done by using the `assert`
keyword that has been added in J2SE 1.4. In Java, `assert` statements can be enabled via the JVM parameters `-ea`
(or `-enableassertions`) and `-da` (or `-disableassertions`). Assertion statements in Java are disabled by default.
Groovy comes with a rather _powerful variant_ of `assert` also known as _power assertion statement_. Groovy's power
`assert` differs from the Java version in its output given the boolean expression validates to `false`:
[source,groovy]
------------------------------------------
def x = 1
assert x == 2
// Output: <1>
//
// Assertion failed:
// assert x == 2
// | |
// 1 false
------------------------------------------
<1> This section shows the std-err output
The `java.lang.AssertionError` that is thrown whenever the assertion can not be validated successfully, contains
an extended version of the original exception message. The power assertion output shows evaluation results from
the outer to the inner expression.
The power assertion statements true power unleashes in complex Boolean statements, or statements with
collections or other `toString`-enabled classes:
[source,groovy]
------------------------------------
def x = [1,2,3,4,5]
assert (x << 6) == [6,7,8,9,10]
// Output:
//
// Assertion failed:
// assert (x << 6) == [6,7,8,9,10]
// | | |
// | | false
// | [1, 2, 3, 4, 5, 6]
// [1, 2, 3, 4, 5, 6]
------------------------------------
Another important difference from Java is that in Groovy assertions are _enabled by default_. It has been a language design
decision to remove the possibility to deactivate assertions. Or, as Bertrand Meyer stated, ``it makes no sense to take
off your swim ring if you put your feet into real water``.
One thing to be aware of are methods with side-effects inside Boolean expressions in power assertion statements. As
the internal error message construction mechanism does only store references to instances under target,
it happens that the error message text is invalid at rendering time in case of side-effecting methods involved:
[source,groovy]
----------------------------------------------------------
assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]
// Output:
//
// Assertion failed:
// assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]
// | | |
// | | false
// | [1, 2, 3, 4]
// [1, 2, 3, 4] // <1>
----------------------------------------------------------
<1> The error message shows the actual state of the collection, not the state before the `unique` method was applied
[NOTE]
If you choose to provide a custom assertion error message this can be done by using the Java syntax `assert
expression1 : expression2` where `expression1` is the Boolean expression and `expression2` is the custom error message.
Be aware though that this will disable the power assert and will fully fallback to custom
error messages on assertion errors.
=== Mocking and Stubbing
Groovy has excellent built-in support for a range of mocking and stubbing alternatives. When using Java, dynamic mocking
frameworks are very popular. A key reason for this is that it is hard work creating custom hand-crafted mocks using Java.
Such frameworks can be used easily with Groovy if you choose but creating custom mocks is much easier in Groovy. You
can often get away with simple maps or closures to build your custom mocks.
The following sections show ways to create mocks and stubs with Groovy language features only.
==== Map Coercion
By using maps or expandos, we can incorporate desired behaviour of a collaborator very easily as shown here:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=map_coercion,indent=0]
----
The `as` operator can be used to coerce a map to a particular class. The given map keys are interpreted as
method names and the values, being `groovy.lang.Closure` blocks, are interpreted as method code blocks.
[NOTE]
Be aware that map coercion can get into the way if you deal with custom `java.util.Map` descendant classes in combination
with the `as` operator. The map coercion mechanism is targeted directly at certain collection classes, it doesn't take
custom classes into account.
==== Closure Coercion
The 'as' operator can be used with closures in a neat way which is great for developer testing in simple scenarios.
We haven't found this technique to be so powerful that we want to do away with dynamic mocking, but it can be very
useful in simple cases none-the-less.
Classes or interfaces holding a single method, including SAM (single abstract method) classes, can be used to coerce
a closure block to be an object of the given type. Be aware that for doing this, Groovy internally create a proxy object
descending for the given class. So the object will not be a direct instance of the given class. This important if, for
example, the generated proxy object's meta-class is altered afterwards.
Let's have an example on coercing a closure to be of a specific type:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=closure_coercion,indent=0]
----
Groovy supports a feature called implicit SAM coercion. This means that the `as` operator is not necessary in situations
where the runtime can infer the target SAM type. This type of coercion might be useful in tests to mock entire SAM
classes:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=sam_coercion,indent=0]
----
==== MockFor and StubFor
The Groovy mocking and stubbing classes can be found in the `groovy.mock.interceptor` package.
The `MockFor` class supports (typically unit) testing of classes in isolation by allowing a _strictly ordered_ expectation
of the behavior of collaborators to be defined. A typical test scenario involves a class under test and one or more collaborators. In such a scenario it is
often desirable to just test the business logic of the class under test. One strategy for doing that is to replace
the collaborator instances with simplified mock objects to help isolate out the logic in the test target. MockFor
allows such mocks to be created using meta-programming. The desired behavior of collaborators is defined as a behavior
specification. The behavior is enforced and checked automatically.
Let's assume our target classes looked like this:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=collaborators,indent=0]
----
With `MockFor`, a mock expectation is always sequence dependent and its use automatically ends with a call to `verify`:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=mockFor,indent=0]
----
<1> a new mock is created by a new instance of `MockFor`
<2> a `Closure` is passed to `use` which enables the mocking functionality
<3> a call to `verify` checks whether the sequence and number of method calls is as expected
The `StubFor` class supports (typically unit) testing of classes in isolation by allowing a _loosely-ordered_ expectation
of the behavior of collaborators to be defined. A typical test scenario involves a class under test and one or more
collaborators. In such a scenario it is often desirable to just test the business logic of the CUT. One strategy for
doing that is to replace the collaborator instances with simplified stub objects to help isolate out the logic
in the target class. `StubFor` allows such stubs to be created using meta-programming. The desired behavior of
collaborators is defined as a behavior specification.
In contrast to `MockFor` the stub expectation checked with `verify` is sequence independent and its use is optional:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=stubFor,indent=0]
----
<1> a new stub is created by a new instance of `StubFor`
<2> the `with` method is used for delegating all calls inside the closure to the `StubFor` instance
<3> a `Closure` is passed to `use` which enables the stubbing functionality
<4> a call to `verify` (optional) checks whether the number of method calls is as expected
`MockFor` and `StubFor` can not be used to test statically compiled classes e.g for Java classes or Groovy classes that
make use of `@CompileStatic`. To stub and/or mock these classes you can use Spock or one of the Java mocking libraries.
[[testing_guide_emc]]
==== Expando Meta-Class (EMC)
Groovy includes a special `MetaClass` the so-called `ExpandoMetaClass` (EMC). It allows to dynamically add methods,
constructors, properties and static methods using a neat closure syntax.
Every `java.lang.Class` is supplied with a special `metaClass` property that will give a reference to an
`ExpandoMetaClass` instance. The expando meta-class is not restricted to custom classes, it can be used for
JDK classes like for example `java.lang.String` as well:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=emc,indent=0]
----
The `ExpandoMetaClass` is a rather good candidate for mocking functionality as it allows for more advanced stuff
like mocking static methods
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=emc2,indent=0]
----
or even constructors
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=emc3,indent=0]
----
[NOTE]
Mocking constructors might seem like a hack that's better not even to be considered but even there might be valid
use cases. An example can be found in Grails where domain class constructors are added at run-time with the
help of `ExpandoMetaClass`. This lets the domain object register itself in the Spring application context and allows
for injection of services or other beans controlled by the dependency-injection container.
If you want to change the `metaClass` property on a per test method level you need to remove the changes that were
done to the meta-class, otherwise those changes would be persistent across test method calls. Changes are removed by
replacing the meta-class in the `GroovyMetaClassRegistry`:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=emc4,indent=0]
----
Another alternative is to register a `MetaClassRegistryChangeEventListener`, track the changed classes and remove
the changes in the cleanup method of your chosen testing runtime. A good example can be found
https://github.com/grails/grails-core/blob/master/grails-bootstrap/src/main/groovy/grails/build/support/MetaClassRegistryCleaner.java[in the Grails web
development framework].
Besides using the `ExpandoMetaClass` on a class-level, there is also support for using the meta-class on a per-object
level:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/MockingExampleTests.groovy[tags=emc5,indent=0]
----
In this case the meta-class change is related to the instance only. Depending on the test scenario this might be a better
fit than the global meta-class change.
=== GDK Methods
The following section gives a brief overview on GDK methods that can be leveraged in test case scenarios, for example for
test data generation.
==== Iterable#combinations
The `combinations` method that is added on `java.lang.Iterable` compliant classes can be used to get a list of
combinations from a list containing two or more sub-lists:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GDKMethodTests.groovy[tags=combinations,indent=0]
----
The method could be used in test case scenarios to generate all possible argument combinations for a specific method
call.
==== Iterable#eachCombination
The `eachCombination` method that is added on `java.lang.Iterable` can be used to apply a function (or in this
case a `groovy.lang.Closure`) to each if the combinations that has been built by the `combinations` method:
`eachCombination` is a GDK method that is added to all classes conforming to the `java.lang.Iterable` interface.
It applies a function on each combination of the input lists:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GDKMethodTests.groovy[tags=each_combination,indent=0]
----
The method could be used in the testing context to call methods with each of the generated combinations.
=== Tool Support
==== Test Code Coverage
Code coverage is a useful measure of the effectiveness of (unit) tests. A program with high code coverage has a
lower chance to hold critical bugs than a program with no or low coverage. To get code coverage metrics,
the generated byte-code usually needs to be instrumented before the tests are executed. One tool with Groovy support
for this task is http://cobertura.github.io/cobertura/[Cobertura].
Various frameworks and build tools come with Cobertura integration. For Grails, there is the http://grails.org/plugin/code-coverage[code coverage plugin] based
on Cobertura, for Gradle there is the https://github.com/eriwen/gradle-cobertura-plugin[gradle-cobertura plugin], to name only two of them.
The following code listing shows an example on how to enable Cobertura test coverage reports in a Gradle build script from
a Groovy project:
[source,groovy]
---------------------------------------------------------------------
def pluginVersion = '<plugin version>'
def groovyVersion = '<groovy version>'
def junitVersion = '<junit version>'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.eriwen:gradle-cobertura-plugin:${pluginVersion}'
}
}
apply plugin: 'groovy'
apply plugin: 'cobertura'
repositories {
mavenCentral()
}
dependencies {
compile "org.codehaus.groovy:groovy-all:${groovyVersion}"
testCompile "junit:junit:${junitVersion}"
}
cobertura {
format = 'html'
includes = ['**/*.java', '**/*.groovy']
excludes = ['com/thirdparty/**/*.*']
}
---------------------------------------------------------------------
Several output formats can be chosen for Cobertura coverage reports and test code coverage reports can be added to
continuous integration build tasks.
== Testing with JUnit
Groovy simplifies JUnit testing in the following ways:
* You use the same overall practices as you would when testing with Java but you
can adopt much of Groovy's concise syntax in your tests making them succinct. You can even use
the capabilities for writing testing domain specific languages (DSLs) if you feel so inclined.
* There are numerous helper classes that simplify many testing activities. The details differ
in some cases depending on the version of JUnit you are using. We'll cover those details shortly.
* Groovy's PowerAssert mechanism is wonderful to use in your tests
* Groovy deems that tests are so important you should be able to run them as easily as scripts or classes.
This is why Groovy includes an automatic test runner when using the `groovy` command or the GroovyConsole.
This gives you some additional options over and above running your tests
In the following sections we will have a closer look at JUnit 3, 4 and 5 Groovy integration.
=== JUnit 3
Maybe one of the most prominent Groovy classes supporting JUnit 3 tests is the `GroovyTestCase` class. Being
derived from `junit.framework.TestCase` it offers a bunch of additional methods that make testing in Groovy a breeze.
[NOTE]
Although `GroovyTestCase` inherits from `TestCase` doesn't mean you can't use JUnit 4 features in your project. In fact,
the most recent Groovy versions come with a bundled JUnit 4 and that comes with a backwards compatible `TestCase`
implementation. There have been some discussion on the Groovy mailing-list on whether to use `GroovyTestCase` or JUnit 4
with the result that it is mostly a matter of taste, but with `GroovyTestCase` you get a bunch of methods for free that
make certain types of tests easier to write.
In this section, we will have a look at some of the methods provided by `GroovyTestCase`. A full list of these can be
found in the JavaDoc documentation for gapi:groovy.test.GroovyTestCase[] ,
don't forget it is inherited from `junit.framework.TestCase` which inherits all the `assert*` methods.
==== Assertion Methods
`GroovyTestCase` is inherited from `junit.framework.TestCase` therefore it inherits a large number of assertion methods
being available to be called in every test method:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=assertions,indent=0]
----
As can be seen above, in contrast to Java it is possible to leave out the parenthesis in most situations which
leads to even more readability of JUnit assertion method call expressions.
An interesting assertion method that is added by `GroovyTestCase` is `assertScript`. It ensures that the given Groovy
code string succeeds without any exception:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=assertScript,indent=0]
----
==== shouldFail Methods
`shouldFail` can be used to check whether the given code block fails or not. In case it fails, the assertion does hold,
otherwise the assertion fails:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=should_fail_without_class,indent=0]
----
The example above uses the basic `shouldFail` method interface that takes a `groovy.lang.Closure` as a single argument.
The `Closure` instance holds the code that is supposed to be breaking during run-time.
If we wanted to assert `shouldFail` on a specific `java.lang.Exception` type we could have done so by using the `shouldFail`
implementation that takes the `Exception` class as first argument and the `Closure` as second argument:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=should_fail_with_class,indent=0]
----
If anything other than `IndexOutOfBoundsException` (or a descendant class of it) is thrown, the test case will fail.
A pretty nice feature of `shouldFail` hasn't been visible so far: it returns the exception message. This is really
useful if you want to assert on the exception error message:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=should_fail_with_msg,indent=0]
----
==== notYetImplemented Method
The `notYetImplemented` method has been greatly influenced by HtmlUnit. It allows to write a test method but mark it
as not yet implemented. As long as the test method fails and is marked with `notYetImplemented` the test goes green:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=not_yet_implemented,indent=0]
----
<1> a call to `notYetImplemented` is necessary for `GroovyTestCase` to get the current method stack.
<2> as long as the test evaluates to `false` the test execution will be successful.
An alternative to the `notYetImplemented` method is the `@NotYetImplemented` annotation. It allows for annotating a
method as not yet implemented, with the exact same behavior as `GroovyTestCase#notYetImplemented` but without the need
for the `notYetImplemented` method call:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/GroovyTestCaseExampleTests.groovy[tags=not_yet_implemented_ast,indent=0]
----
=== JUnit 4
Groovy can be used to write JUnit 4 test cases without any restrictions. The `groovy.test.GroovyAssert` holds
various static methods that can be used as replacement for the `GroovyTestCase` methods in JUnit 4 tests:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/JUnit4ExampleTests.groovy[tags=junit4_example,indent=0]
----
As can be seen in the example above, the static methods found in `GroovyAssert` are imported at the beginning of the
class definition thus `shouldFail` can be used the same way it can be used in a `GroovyTestCase`.
[NOTE]
`groovy.test.GroovyAssert` descends from `org.junit.Assert` that means it inherits all JUnit assertion methods. However,
with the introduction of the power assertion statement, it turned out to be _good practice to rely on assertion statements_
instead of using the JUnit assertion methods with the improved message being the main reason.
It is worth mentioning that `GroovyAssert.shouldFail` is not absolutely identical to `GroovyTestCase.shouldFail`. While
`GroovyTestCase.shouldFail` returns the exception message, `GroovyAssert.shouldFail` returns the exception itself. It
takes a few more keystrokes to get the message, but in return you can access other properties and methods of the
exception:
[source,groovy]
----
include::{projectdir}/src/spec/test/testingguide/JUnit4ExampleTests.groovy[tags=should_fail_return,indent=0]
----
include::{rootProjectDir}/subprojects/groovy-test-junit5/src/spec/doc/testing-with-junit5.adoc[leveloffset=+1]
== Testing with Spock
Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the
crowd is its beautiful and highly expressive specification DSL. In practice, Spock specifications are written as
Groovy classes. Although written in Groovy they can be used to test Java classes. Spock can be used for unit,
integration or BDD (behavior-driven-development) testing, it doesn't put itself into a specific category of testing
frameworks or libraries.
[NOTE]
Beside these awesome features Spock is a good example on how to leverage advanced Groovy programming
language features in third party libraries, for example, by using Groovy AST transformations.
[NOTE]
This section should not serve as detailed guide on how to use Spock, it should rather give an impression what Spock
is about and how it can be leveraged for unit, integration, functional or any other type of testing.
The next section we will have an first look at the anatomy of a Spock specification. It should give a
pretty good feeling on what Spock is up to.
=== Specifications
Spock lets you write specifications that describe features (properties, aspects) exhibited by a system of
interest. The "system" can be anything between a single class and an entire application, a more advanced term for it is
_system under specification_. The _feature description_ starts from a specific snapshot of the system and its
collaborators, this snapshot is called the _feature's fixture_.
Spock specification classes are derived from `spock.lang.Specification`. A concrete specification class might consist
of fields, fixture methods, features methods and helper methods.
Let's have a look at a simple specification with a single feature method for an imaginary `Stack` class:
[source,groovy]
---------------------------------------------------------------
class StackSpec extends Specification {
def "adding an element leads to size increase"() { // <1>
setup: "a new stack instance is created" // <2>
def stack = new Stack()
when: // <3>
stack.push 42
then: // <4>
stack.size() == 1
}
}
---------------------------------------------------------------
<1> Feature method, is by convention named with a String literal.
<2> Setup block, here is where any setup work for this feature needs to be done.
<3> When block describes a stimulus, a certain action under target by this feature specification.
<4> Then block any expressions that can be used to validate the result of the code that was triggered by the when block.
Spock feature specifications are defined as methods inside a `spock.lang.Specification` class. They describe the feature
by using a String literal instead of a method name.
A feature method holds multiple blocks, in our example we used `setup`, `when` and `then`. The `setup` block is special
in that it is optional and allows to configure local variables visible inside the feature method. The `when` block
defines the stimulus and is a companion of the `then` block which describes the response to the stimulus.
Note that the `setup` method in the `StackSpec` above additionally
has a description String. Description Strings are optional and can be added after block labels (like `setup`, `when`,
`then`).
=== More Spock
Spock provides much more features like data tables or advanced mocking capabilities. Feel free to consult the
https://github.com/spockframework/spock[Spock GitHub page] for more documentation and download information.
== Functional Tests with Geb
Geb is a functional web testing and scraper library that integrates with JUnit and Spock. It is based upon the
Selenium web drivers and, like Spock, provides a Groovy DSL to write functional tests for web applications.
Geb has great features that make it a good fit for a functional testing library:
* DOM access via a JQuery-like `$` function
* implements the _page pattern_
* support for modularization of certain web components (e.g. menu-bars, etc.) with _modules_
* integration with JavaScript via the JS variable
[NOTE]
This section should not serve as detailed guide on how to use Geb, it should rather give an impression what Geb
is about and how it can be leveraged functional testing.
The next section will give an example on how Geb can be used to write a functional test for a simple
web page with a single search field.
=== A Geb Script
Although Geb can be used standalone in a Groovy script, in many scenarios it's used in combination with other testing
frameworks. Geb comes with various base classes that can be used in JUnit 3, 4, TestNG or Spock tests. The base classes
are part of additional Geb modules that need to be added as a dependency.
For example, the following `@Grab` dependencies can be used to run Geb with the Selenium Firefox driver in
JUnit4 tests. The module that is needed for JUnit 3/4 support is `geb-junit4`:
[source,groovy]
--------------------------------------------------------------------
@Grab('org.gebish:geb-core:0.9.2')
@Grab('org.gebish:geb-junit4:0.9.2')
@Grab('org.seleniumhq.selenium:selenium-firefox-driver:2.26.0')
@Grab('org.seleniumhq.selenium:selenium-support:2.26.0')
--------------------------------------------------------------------
The central class in Geb is the `geb.Browser` class. As its name implies it is used
to browse pages and access DOM elements:
[source,groovy]
---------------------------------------------------------------------------------------------------
import geb.Browser
import org.openqa.selenium.firefox.FirefoxDriver
def browser = new Browser(driver: new FirefoxDriver(), baseUrl: 'http://myhost:8080/myapp') // <1>
browser.drive {
go "/login" // <2>
$("#username").text = 'John' // <3>
$("#password").text = 'Doe'
$("#loginButton").click()
assert title == "My Application - Dashboard"
}
---------------------------------------------------------------------------------------------------
<1> A new `Browser` instance is created. In this case it uses the Selenium `FirefoxDriver` and sets the `baseUrl`.
<2> `go` is used to navigate to an URL or relative URI
<3> `$` together with CSS selectors is used to access the `username` and `password` DOM fields.
The `Browser` class comes with a `drive` method that delegates all method/property calls to the current
`browser` instance. The `Browser` configuration must not be done inline, it can also be externalized in a
`GebConfig.groovy` configuration file for example. In practice, the usage of the `Browser` class is mostly hidden
by Geb test base classes. They delegate all missing properties and method calls to the current `browser` instance
that exists in the background:
[source,groovy]
---------------------------------------------------------------
class SearchTests extends geb.junit4.GebTest {
@Test
void executeSeach() {
go 'http://somehost/mayapp/search' // <1>
$('#searchField').text = 'John Doe' // <2>
$('#searchButton').click() // <3>
assert $('.searchResult a').first().text() == 'Mr. John Doe' // <4>
}
}
---------------------------------------------------------------
<1> `Browser#go` takes a relative or absolute link and calls the page.
<2> `Browser#$` is used to access DOM content. Any CSS selectors supported by the underlying Selenium drivers are allowed
<3> `click` is used to click a button.
<4> `$` is used to get the first link out of the `searchResult` block
The example above shows a simple Geb web test with the JUnit 4 base class `geb.junit4.GebTest`. Note that in this case
the `Browser` configuration is externalized. `GebTest` delegates methods like `go` and `$` to the underlying `browser`
instance.
=== More Geb
In the previous section we only scratched the surface of the available Geb features. More information on Geb can be found
at the http://gebish.org[project homepage].