Cucumber World with Groovy

Monday, May 3rd, 2010

Cucumber provides the ability to create a world in which you can expose useful methods for the testing environment. This post briefly details how to expose these methods when using Groovy with Cuke4Duke.

Feature: World
In order to make useful methods available
As a tester
I want the world to be full of wonderful methods

Scenario: Greet
When I greet
...

Scenario: Farewell
When I farewell
...

These scenarios capture the ability to greet and farewell our customers. The matching step definitions are shown below.

// hello_steps.groovy
import static org.junit.Assert.*
import static org.hamcrest.CoreMatchers.*

this.metaClass.mixin(cuke4duke.GroovyDsl)

When(~'I greet') {
  assertThat(hello(), is('hi'))
}

When(~'I farewell') {
  assertThat(goodbye(), is('ciao'))
}

You will notice that we haven’t defined the hello() or goodbye() methods anywhere in our step definition file. We are assuming these methods are provided by the world environment that Cucumber provides. This allows the world methods to be shared between many different step definition files.

Next we define the world environment. Instead of defining all methods in a single class, we can group the methods into individual classes and make them all available on the world class at runtime using Groovy’s mixin support.

// env.groovy
this.metaClass.mixin(cuke4duke.GroovyDsl)

class Greeter {
  def hello() {
    'hi'
  }
}

class Fareweller {
  def goodbye() {
    'ciao'
  }
}

class World {}

World() {
  World.mixin Greeter, Fareweller
  new World()
}

The World() call is part of Cucumber’s DSL, here we use it to publish an instance of our World class thus making both hello() and goodbye() available in the testing environment (note that the name of the World class is not important).

Tags: , , , , ,

7 Comments to Cucumber World with Groovy

Richard Paul
May 4, 2010

Alternatively you can skip the empty World class definition and replace:

World() {
  World.mixin Greeter, Fareweller
  new World()
}

with:

World() {
  def world = new Object()
  world.metaClass.mixin Greeter, Fareweller
  world
}
Andrew Sondgeroth
July 28, 2010

I’ve been developing in Java for 10 years but I’m as green as cuke when it comes to using Cucumber, JRuby, Groovy and Cuke4Duke.

One (very basic) question I have is how do you reuse/inherit/reference various .groovy files? For example:

//step1.groovy
this.metaClass.mixin(cuke4duke.GroovyDsl)

String value

Given(~”I have the value (.*)”) { String val ->
value = val
}

When(~”I concatenate (.*)”) { String val ->
value += val
}

//steps2.groovy
this.metaClass.mixin(cuke4duke.GroovyDsl)

Then(~”I get (.*)”) { String expected ->
//How do I access ‘value’?
assert value == expected
}

//some.feature
Feature: fooBar

Scenario: Accessibility
Given I have the value foo
When I concatenate Bar
Then I get fooBar

Obviously this is a very basic example but as we produce more and more steps/features/scenarios it would be useful to group or reuse some of our step definitions. I can’t seem to find documentation on how to do that and all of my attempts at guessing have failed.

Thanks!

Richard Paul
July 28, 2010

When Cucumber runs it actually collates all the step definitions from all the groovy files into a single source. So using steps from multiple definition files as you have shown should work.

I will quite often have a step definition file that has common functionality used across many different aspects of the site. The various scenarios reference them without problem.

Make sure you have the groovy files inside the step_definition directory, check http://github.com/aslakhellesoy/cuke4duke/tree/master/examples/groovy-webdriver/features/ for an example project layout.

Andrew Sondgeroth
July 28, 2010

Wow! Thanks for the prompt response.

I believe I have the project layout correct. I can reference steps in multiple groovy files with no problem. However, when I try to reference the ‘value’ property in steps1.groovy from steps2.groovy (as noted above) then I get the following error:

groovy.lang.MissingPropertyException: No such property: value for class: steps1 (NativeException)

The ‘value’ property is successfully accessed in the Given and When (in steps1.groovy) but the exception occurs in the Then (in steps2.groovy).

So, I wasn’t sure what the accessibility/scope rules were. More accurately, I wasn’t sure how cucumber was handling multiple files (i.e. do they get aggregated into one class or does it simply “search” through the steps in various definitions until it finds a match).

Thanks again!

Andrew Sondgeroth
July 28, 2010

Sorry to elaborate on my problem if I have:
//steps.groovy
Then(~”I get (.*)”) { String expected ->
assert value == expected
}

The error says the property is of course missing in steps2. So, I tried to be clever with something like:

Then(~”I get (.*)”) { String expected ->
assert steps1.value == expected
}

Then it says the property is missing from steps1. (I also tried to use the mixin of the steps1 definition…which also failed).

…My newbie-ness must be blinding.

Richard Paul
July 28, 2010

Ah, I totally missed the variable assignment. Sharing of variables between step definition files won’t work (I think each file is compiled into a separate class).

If you left the type off the variable it would instead be bound to the script binding and be available in all groovy scripts but I don’t think this is a tidy approach. I would try to avoid stateful steps, and if they are necessary try to co-locate them in a single step definition file.

It might be better to voice this to a wider audience on the Google group: http://groups.google.com/group/cukes

Cheers,
Richard

Andrew Sondgeroth
July 28, 2010

Thank you, Richard!

I didn’t think properties would be readily available to other steps. But, given what we’re trying to do in our project I thought it might be useful. We seem to be trending towards a monolithic definition file for our tests (vs. lots of copy-paste of similar behavior into separate files).

I will check out the Google group. I figured there had to be a forum out there somewhere. I just hadn’t come across it. I’ll go bug those guys. :)

Thanks again.

Leave a comment